Initial ThinkPad Hyprland dotfiles

This commit is contained in:
Pascal
2026-04-28 03:59:07 +02:00
commit 6eb922c417
56 changed files with 6587 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
HYPR_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
MODE="${1:-wallpaper}"
export HYPR_DIR
export HYPR_SWITCHER_THEME_DIR="$HYPR_DIR/Themes"
export HYPR_SWITCHER_WALLPAPER_DIR="${WALLPAPER_DIR:-$HOME/Bilder/Wallpaper}"
notify() {
notify-send "AGS Switcher" "$1" >/dev/null 2>&1 || true
}
if ! command -v ags >/dev/null 2>&1; then
notify "ags ist nicht installiert."
exit 1
fi
cd "$HYPR_DIR"
ags quit --instance hypr-switcher >/dev/null 2>&1 || true
exec ags run "$HYPR_DIR/ags/switcher.tsx" "$MODE"

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
choice="$(
printf '%s\n' \
"󰌪 Theme wechseln" \
"󰸉 Wallpaper wechseln" |
wofi --dmenu --prompt "󰉼 Aussehen" --insensitive
)"
case "$choice" in
*"Theme wechseln"*)
"$SCRIPT_DIR/ags-switcher.sh" theme
;;
*"Wallpaper wechseln"*)
"$SCRIPT_DIR/ags-switcher.sh" wallpaper
;;
esac

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -euo pipefail
notify() {
notify-send "󰕾 Audio" "$1"
}
require_wpctl() {
if ! command -v wpctl >/dev/null 2>&1; then
notify "wpctl ist nicht installiert."
exit 1
fi
}
choose_sink() {
wpctl status |
awk '
/Sinks:/ {in_sinks=1; next}
/Sources:/ {in_sinks=0}
in_sinks && /\*/ {gsub(/^[[:space:]]*[│├└─* ]*/, ""); print "󰓃 " $0}
in_sinks && /^[[:space:]]*[│├└─ ]*[0-9]+\./ {gsub(/^[[:space:]]*[│├└─ ]*/, ""); print "󰓃 " $0}
' |
wofi --dmenu --prompt "󰕾 Audioausgabe" --insensitive
}
require_wpctl
choice="$(
printf '%s\n' \
"󰝝 Lauter" \
"󰝞 Leiser" \
"󰖁 Stumm schalten" \
"󰓃 Ausgabe wechseln" \
"󰍬 Mikrofon stumm" \
"󰎆 Play/Pause" \
"󰒮 Naechster Titel" \
"󰒭 Vorheriger Titel" \
"󰩟 Status" |
wofi --dmenu --prompt "󰕾 Audio" --insensitive
)"
case "$choice" in
*"Lauter"*)
wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
;;
*"Leiser"*)
wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
;;
*"Stumm schalten"*)
wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
;;
*"Ausgabe wechseln"*)
selection="$(choose_sink)"
[ -n "$selection" ] || exit 0
sink_id="$(printf '%s\n' "$selection" | sed -n 's/.* \([0-9][0-9]*\)\..*/\1/p')"
[ -n "$sink_id" ] || exit 0
wpctl set-default "$sink_id" && notify "Audioausgabe gewechselt."
;;
*"Mikrofon stumm"*)
wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
;;
*"Play/Pause"*)
playerctl play-pause
;;
*"Naechster Titel"*)
playerctl next
;;
*"Vorheriger Titel"*)
playerctl previous
;;
*"Status"*)
wpctl status | wofi --dmenu --prompt "󰩟 Audiostatus"
;;
esac

View File

@@ -0,0 +1,133 @@
#!/usr/bin/env bash
set -euo pipefail
notify() {
notify-send "󰂯 Bluetooth" "$1"
}
require_bluetoothctl() {
if ! command -v bluetoothctl >/dev/null 2>&1; then
notify "bluetoothctl ist nicht installiert."
exit 1
fi
}
wofi_pick() {
wofi --dmenu --prompt "$1" --insensitive
}
adapter_powered() {
{ bluetoothctl show 2>/dev/null || true; } | awk -F': ' '/Powered/ {print $2; exit}'
}
device_name() {
local mac="$1"
bluetoothctl info "$mac" 2>/dev/null | awk -F': ' '/Name/ {print $2; exit}'
}
pick_device() {
local prompt="$1"
bluetoothctl devices |
awk '{mac=$2; $1=""; $2=""; sub(/^ */, ""); printf "󰂯 %s [%s]\n", $0, mac}' |
wofi_pick "$prompt"
}
selected_mac() {
sed -n 's/.*\[\([0-9A-Fa-f:]\{17\}\)\].*/\1/p'
}
scan_devices() {
notify "Scan laeuft fuer 8 Sekunden."
timeout 8 bluetoothctl scan on >/dev/null 2>&1 || true
bluetoothctl scan off >/dev/null 2>&1 || true
local selection mac name
selection="$(pick_device "󰂯 Geraet verbinden")"
[ -n "$selection" ] || exit 0
mac="$(printf '%s\n' "$selection" | selected_mac)"
name="$(device_name "$mac")"
[ -n "$name" ] || name="$mac"
bluetoothctl pair "$mac" >/dev/null 2>&1 || true
bluetoothctl trust "$mac" >/dev/null 2>&1 || true
bluetoothctl connect "$mac" && notify "Verbunden mit $name."
}
connect_saved_device() {
local selection mac name
selection="$(pick_device "󰛳 Gekoppeltes Geraet verbinden")"
[ -n "$selection" ] || exit 0
mac="$(printf '%s\n' "$selection" | selected_mac)"
name="$(device_name "$mac")"
[ -n "$name" ] || name="$mac"
bluetoothctl connect "$mac" && notify "Verbunden mit $name."
}
disconnect_device() {
local selection mac name
selection="$(
bluetoothctl devices Connected |
awk '{mac=$2; $1=""; $2=""; sub(/^ */, ""); printf "󰂯 %s [%s]\n", $0, mac}' |
wofi_pick "󰅖 Bluetooth trennen"
)"
[ -n "$selection" ] || exit 0
mac="$(printf '%s\n' "$selection" | selected_mac)"
name="$(device_name "$mac")"
[ -n "$name" ] || name="$mac"
bluetoothctl disconnect "$mac" && notify "$name getrennt."
}
remove_device() {
local selection mac name
selection="$(pick_device "󰆴 Geraet entfernen")"
[ -n "$selection" ] || exit 0
mac="$(printf '%s\n' "$selection" | selected_mac)"
name="$(device_name "$mac")"
[ -n "$name" ] || name="$mac"
bluetoothctl remove "$mac" && notify "$name entfernt."
}
require_bluetoothctl
power="$(adapter_powered)"
[ -n "$power" ] || power="unknown"
choice="$(
printf '%s\n' \
"󰂯 Geraet suchen und verbinden" \
"󰂲 Bluetooth ein/aus" \
"󰛳 Gekoppelte Geraete" \
"󰅖 Verbundenes Geraet trennen" \
"󰆴 Geraet entfernen" \
"󰩟 Status: $power" |
wofi_pick "󰂯 Bluetooth"
)"
case "$choice" in
*"Geraet suchen"*)
bluetoothctl power on >/dev/null
bluetoothctl agent on >/dev/null 2>&1 || true
bluetoothctl default-agent >/dev/null 2>&1 || true
scan_devices
;;
*"Bluetooth ein/aus"*)
if [ "$power" = "yes" ]; then
bluetoothctl power off && notify "Bluetooth ausgeschaltet."
else
bluetoothctl power on && notify "Bluetooth eingeschaltet."
fi
;;
*"Gekoppelte Geraete"*)
connect_saved_device
;;
*"Verbundenes Geraet trennen"*)
disconnect_device
;;
*"Geraet entfernen"*)
remove_device
;;
*"Status:"*)
bluetoothctl show | wofi --dmenu --prompt "󰩟 Bluetoothstatus"
;;
esac

369
config/hypr/Scripts/dev-menu.sh Executable file
View File

@@ -0,0 +1,369 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOTS=(
"$HOME/Projekte"
"$HOME/Projects"
"$HOME/Code"
"$HOME/Developer"
"$HOME/dev"
"$HOME/test"
)
notify() {
notify-send "󰅩 Dev Menue" "$1" >/dev/null 2>&1 || true
}
pick() {
wofi --dmenu --prompt "$1" --insensitive
}
terminal_hold() {
local title="$1"
shift
if command -v kitty >/dev/null 2>&1; then
kitty --title "$title" sh -lc "$*; printf '\n'; read -r -p 'Enter zum Schliessen... ' _"
else
notify "kitty ist nicht installiert."
fi
}
shell_quote() {
printf '%q' "$1"
}
code_cmd() {
if command -v code >/dev/null 2>&1; then
printf '%s\n' code
elif command -v codium >/dev/null 2>&1; then
printf '%s\n' codium
else
return 1
fi
}
docker_available() {
if ! command -v docker >/dev/null 2>&1; then
notify "Docker ist nicht installiert."
return 1
fi
if ! docker info >/dev/null 2>&1; then
notify "Docker laeuft nicht oder ist nicht erreichbar."
return 1
fi
}
has_compose() {
local project="$1"
[[ -f "$project/compose.yml" ||
-f "$project/compose.yaml" ||
-f "$project/docker-compose.yml" ||
-f "$project/docker-compose.yaml" ]]
}
project_label() {
local project="$1"
local marker=""
if [[ -d "$project/.git" ]]; then
marker+=" "
fi
if has_compose "$project"; then
marker+="󰡨 "
fi
printf '%s%s' "$marker" "${project#$HOME/}"
}
pick_project() {
local roots=()
local projects=()
local labels=()
local root project choice
for root in "${PROJECT_ROOTS[@]}"; do
[[ -d "$root" ]] && roots+=("$root")
done
if ((${#roots[@]} == 0)); then
notify "Keine Projektordner gefunden."
return 1
fi
mapfile -t projects < <(
{
find "${roots[@]}" -maxdepth 4 -type d -name .git -printf '%h\n' 2>/dev/null
find "${roots[@]}" -maxdepth 3 -type d \( -name node_modules -o -name vendor \) -prune -o \
-type f \( -name compose.yml -o -name compose.yaml -o -name docker-compose.yml -o -name docker-compose.yaml -o -name package.json -o -name Cargo.toml -o -name pyproject.toml -o -name go.mod \) \
-printf '%h\n' 2>/dev/null
} |
sort -u
)
if ((${#projects[@]} == 0)); then
notify "Keine Projekte gefunden."
return 1
fi
for project in "${projects[@]}"; do
labels+=("$(project_label "$project")")
done
choice="$(printf '%s\n' "${labels[@]}" | pick "📁 Projekt")"
[[ -z "${choice:-}" ]] && return 1
for i in "${!labels[@]}"; do
if [[ "$choice" == "${labels[$i]}" ]]; then
printf '%s\n' "${projects[$i]}"
return 0
fi
done
return 1
}
open_project_browser() {
local project="$1"
local choice url
choice="$(
printf '%s\n' \
"🌐 http://localhost:3000" \
"🌐 http://localhost:5173" \
"🌐 http://localhost:8000" \
"🌐 http://localhost:8080" \
"🌐 http://localhost:5000" \
"🌐 http://localhost:4200" \
"📂 Projektordner im Browser" |
pick "🌐 Projekt im Browser"
)"
case "$choice" in
*"Projektordner"*)
xdg-open "$project" >/dev/null 2>&1 &
;;
*"http"*)
url="${choice#* }"
xdg-open "$url" >/dev/null 2>&1 &
;;
esac
}
project_menu() {
local project project_q choice editor
project="$(pick_project)" || return 0
project_q="$(shell_quote "$project")"
choice="$(
printf '%s\n' \
"📂 Projekt oeffnen (Code)" \
"🌐 Projekt im Browser oeffnen" \
"🐳 Docker Compose starten" \
"🛑 Docker stoppen" \
"🔄 Container neu starten" \
"📜 Logs anzeigen" \
"📦 Git Repo Status anzeigen" |
pick "📁 $(basename "$project")"
)"
case "$choice" in
*"Projekt oeffnen"*)
if editor="$(code_cmd)"; then
"$editor" "$project" >/dev/null 2>&1 &
else
notify "VS Code/Codium ist nicht installiert."
fi
;;
*"Browser"*)
open_project_browser "$project"
;;
*"Docker Compose starten"*)
docker_available || return 0
if has_compose "$project"; then
terminal_hold "Docker Compose: $(basename "$project")" "cd $project_q && docker compose up -d && docker compose ps"
else
notify "Kein Docker-Compose File im Projekt."
fi
;;
*"Docker stoppen"*)
docker_available || return 0
if has_compose "$project"; then
terminal_hold "Docker Stop: $(basename "$project")" "cd $project_q && docker compose stop && docker compose ps"
else
notify "Kein Docker-Compose File im Projekt."
fi
;;
*"Container neu starten"*)
docker_available || return 0
if has_compose "$project"; then
terminal_hold "Docker Restart: $(basename "$project")" "cd $project_q && docker compose restart && docker compose ps"
else
notify "Kein Docker-Compose File im Projekt."
fi
;;
*"Logs anzeigen"*)
docker_available || return 0
if has_compose "$project"; then
if command -v kitty >/dev/null 2>&1; then
kitty --title "Logs: $(basename "$project")" sh -lc "cd $project_q && docker compose logs -f --tail=200"
else
notify "kitty ist nicht installiert."
fi
else
notify "Kein Docker-Compose File im Projekt."
fi
;;
*"Git Repo Status"*)
if [[ -d "$project/.git" ]]; then
terminal_hold "Git Status: $(basename "$project")" "cd $project_q && git status --short --branch"
else
notify "Kein Git Repository."
fi
;;
esac
}
pick_container() {
local containers=()
local labels=()
local line name status choice
docker_available || return 1
mapfile -t containers < <(docker ps -a --format '{{.Names}}|{{.Status}}' | sort)
if ((${#containers[@]} == 0)); then
notify "Keine Docker Container gefunden."
return 1
fi
for line in "${containers[@]}"; do
name="${line%%|*}"
status="${line#*|}"
labels+=("🐳 $name · $status")
done
choice="$(printf '%s\n' "${labels[@]}" | pick "🐳 Container")"
[[ -z "${choice:-}" ]] && return 1
for i in "${!labels[@]}"; do
if [[ "$choice" == "${labels[$i]}" ]]; then
printf '%s\n' "${containers[$i]%%|*}"
return 0
fi
done
return 1
}
docker_menu() {
local choice container running_ids all_ids
choice="$(
printf '%s\n' \
"▶️ Alle Container starten" \
"⏹️ Alle stoppen" \
"🔄 Einzelnen Container neu starten" \
"📊 docker stats" \
"📜 Logs anzeigen" \
"🧹 Cleanup dangling images" |
pick "🐳 Docker Control"
)"
case "$choice" in
*"Alle Container starten"*)
docker_available || return 0
all_ids="$(docker ps -aq)"
if [[ -n "$all_ids" ]]; then
terminal_hold "Docker Start" "docker start $all_ids && docker ps"
else
notify "Keine Container gefunden."
fi
;;
*"Alle stoppen"*)
docker_available || return 0
running_ids="$(docker ps -q)"
if [[ -n "$running_ids" ]]; then
terminal_hold "Docker Stop" "docker stop $running_ids && docker ps -a"
else
notify "Keine laufenden Container."
fi
;;
*"Einzelnen Container neu starten"*)
container="$(pick_container)" || return 0
terminal_hold "Docker Restart: $container" "docker restart '$container' && docker ps -a --filter name='^/$container$'"
;;
*"docker stats"*)
docker_available || return 0
if command -v kitty >/dev/null 2>&1; then
kitty --title "docker stats" sh -lc "docker stats"
else
notify "kitty ist nicht installiert."
fi
;;
*"Logs anzeigen"*)
container="$(pick_container)" || return 0
if command -v kitty >/dev/null 2>&1; then
kitty --title "Logs: $container" sh -lc "docker logs -f --tail=200 '$container'"
else
notify "kitty ist nicht installiert."
fi
;;
*"Cleanup dangling images"*)
docker_available || return 0
terminal_hold "Docker Cleanup" "docker image prune -f"
;;
esac
}
choice="$(
printf '%s\n' \
"📁 Projekt Management" \
"󰌘 Homelab Controlcenter" \
"🐳 Docker Control" \
" Terminal" \
" Projektordner" \
" VS Code / Codium" \
"󰊢 Git GUI" |
pick "󰅩 Dev Menue"
)"
case "$choice" in
*"Projekt Management"*)
project_menu
;;
*"Homelab Controlcenter"*)
"$SCRIPT_DIR/homelab-control.sh"
;;
*"Docker Control"*)
docker_menu
;;
*"Terminal"*)
kitty
;;
*"Projektordner"*)
nautilus
;;
*"VS Code"*|*"Codium"*)
if editor="$(code_cmd)"; then
"$editor" >/dev/null 2>&1 &
else
notify "VS Code/Codium ist nicht installiert."
fi
;;
*"Git GUI"*)
if command -v gitg >/dev/null 2>&1; then
gitg
elif command -v git-cola >/dev/null 2>&1; then
git-cola
else
notify "gitg oder git-cola ist nicht installiert."
fi
;;
esac

View File

@@ -0,0 +1,145 @@
#!/usr/bin/env bash
set -euo pipefail
notify() {
notify-send "󰍹 Display" "$1"
}
wofi_pick() {
wofi --dmenu --prompt "$1" --insensitive
}
connected_monitors() {
hyprctl monitors all 2>/dev/null | awk '
/^Monitor / {
name=$2
disabled=0
}
/disabled: true/ {
disabled=1
}
/^$/ && name != "" {
if (!disabled) print name
name=""
}
END {
if (name != "" && !disabled) print name
}
'
}
all_monitors() {
hyprctl monitors all 2>/dev/null | awk '/^Monitor / {print $2}'
}
first_monitor() {
connected_monitors | head -n 1
}
second_monitor() {
connected_monitors | sed -n '2p'
}
show_status() {
hyprctl monitors all | wofi --dmenu --prompt "󰩟 Displaystatus"
}
choose_monitor() {
local monitors
monitors="$(all_monitors)"
if [ -z "$monitors" ]; then
notify "Keine Monitore ueber hyprctl gefunden."
exit 0
fi
printf '%s\n' "$monitors" | awk '{print "󰍹 " $0}' | wofi_pick "$1"
}
disable_monitor() {
local selection monitor
selection="$(choose_monitor "󰍹 Monitor deaktivieren")"
[ -n "$selection" ] || exit 0
monitor="${selection#* }"
hyprctl keyword monitor "$monitor,disable" && notify "$monitor deaktiviert."
}
enable_preferred() {
local selection monitor
selection="$(choose_monitor "󰍹 Monitor aktivieren")"
[ -n "$selection" ] || exit 0
monitor="${selection#* }"
hyprctl keyword monitor "$monitor,preferred,auto,1" && notify "$monitor aktiviert."
}
extend_right() {
local primary secondary
primary="$(first_monitor)"
secondary="$(second_monitor)"
if [ -z "$primary" ] || [ -z "$secondary" ]; then
notify "Dafuer muessen mindestens zwei aktive Monitore vorhanden sein."
exit 0
fi
hyprctl keyword monitor "$primary,preferred,0x0,1"
hyprctl keyword monitor "$secondary,preferred,auto-right,1"
notify "Displays erweitert."
}
mirror_displays() {
local primary secondary
primary="$(first_monitor)"
secondary="$(second_monitor)"
if [ -z "$primary" ] || [ -z "$secondary" ]; then
notify "Dafuer muessen mindestens zwei aktive Monitore vorhanden sein."
exit 0
fi
hyprctl keyword monitor "$primary,preferred,0x0,1"
hyprctl keyword monitor "$secondary,preferred,0x0,1,mirror,$primary"
notify "Displays gespiegelt."
}
choice="$(
printf '%s\n' \
"󰍹 Status anzeigen" \
"󰑓 Display-Konfig neu laden" \
"󰹑 Monitor aktivieren" \
"󰶐 Monitor deaktivieren" \
"󰹑 Displays erweitern" \
"󰹑 Displays spiegeln" \
"󰍹 Grafisches Display-Tool" |
wofi_pick "󰍹 Display"
)"
case "$choice" in
*"Status anzeigen"*)
show_status
;;
*"Display-Konfig neu laden"*)
hyprctl reload
;;
*"Monitor aktivieren"*)
enable_preferred
;;
*"Monitor deaktivieren"*)
disable_monitor
;;
*"Displays erweitern"*)
extend_right
;;
*"Displays spiegeln"*)
mirror_displays
;;
*"Grafisches Display-Tool"*)
if command -v nwg-displays >/dev/null 2>&1; then
nwg-displays
elif command -v wdisplays >/dev/null 2>&1; then
wdisplays
else
notify "Installiere nwg-displays oder wdisplays fuer ein grafisches Display-Tool."
fi
;;
esac

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
HYPR_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
notify() {
notify-send "Homelab" "$1" >/dev/null 2>&1 || true
}
if ! command -v ags >/dev/null 2>&1; then
notify "ags ist nicht installiert."
exit 1
fi
if ! command -v sshpass >/dev/null 2>&1; then
notify "sshpass ist nicht installiert."
exit 1
fi
cd "$HYPR_DIR"
ags quit --instance homelab-control >/dev/null 2>&1 || true
exec ags run "$HYPR_DIR/ags/homelab.tsx"

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
if pgrep -x wofi >/dev/null; then
pkill -x wofi
exit 0
fi
choice="$(
printf '%s\n' \
"󰀻 Apps" \
"󰅩 Dev Menue" \
"󰒓 Einstellungen" \
" Terminal" \
" Dateien" \
"󰑓 Hyprland neu laden" \
"󰍃 Session beenden" |
wofi --dmenu --prompt "󰣇 Hauptmenue" --insensitive
)"
case "$choice" in
*"Apps"*)
wofi --show drun
;;
*"Dev Menue"*)
"$SCRIPT_DIR/dev-menu.sh"
;;
*"Einstellungen"*)
"$SCRIPT_DIR/settings-menu.sh"
;;
*"Terminal"*)
kitty
;;
*"Dateien"*)
nautilus
;;
*"Hyprland neu laden"*)
hyprctl reload
;;
*"Session beenden"*)
if command -v hyprshutdown >/dev/null 2>&1; then
hyprshutdown
else
hyprctl dispatch exit
fi
;;
esac

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -euo pipefail
notify() {
notify-send "󰤨 Netzwerk" "$1"
}
require_nmcli() {
if ! command -v nmcli >/dev/null 2>&1; then
notify "nmcli ist nicht installiert."
exit 1
fi
}
wofi_pick() {
wofi --dmenu --prompt "$1" --insensitive
}
wifi_status() {
nmcli -t -f WIFI general | head -n 1
}
active_connection() {
nmcli -t -f NAME,TYPE connection show --active | awk -F: '$2 ~ /wireless|ethernet/ {print $1; exit}'
}
connect_wifi() {
nmcli radio wifi on
nmcli device wifi rescan >/dev/null 2>&1 || true
local selection ssid security password
selection="$(
nmcli -t -f IN-USE,SSID,SECURITY,SIGNAL device wifi list |
awk -F: '
$2 != "" {
icon = $1 == "*" ? "󰁥" : "󰤨"
lock = $3 == "" ? "offen" : "gesichert"
printf "%s %s [%s, %s%%]\n", icon, $2, lock, $4
}
' |
wofi_pick "󰤨 WLAN verbinden"
)"
[ -n "$selection" ] || exit 0
ssid="$(printf '%s\n' "$selection" | sed -E 's/^[^ ]+ //; s/ \[.*$//')"
security="$(nmcli -t -f SSID,SECURITY device wifi list | awk -F: -v ssid="$ssid" '$1 == ssid {print $2; exit}')"
if [ -n "$security" ]; then
password="$(printf '' | wofi --dmenu --password --prompt "󰌾 Passwort fuer $ssid")"
[ -n "$password" ] || exit 0
nmcli device wifi connect "$ssid" password "$password" && notify "Verbunden mit $ssid."
else
nmcli device wifi connect "$ssid" && notify "Verbunden mit $ssid."
fi
}
saved_connections() {
local selection name
selection="$(
nmcli -t -f NAME,TYPE connection show |
awk -F: '$2 ~ /wireless|ethernet/ {printf "󰛳 %s\n", $1}' |
wofi_pick "󰛳 Gespeicherte Verbindungen"
)"
[ -n "$selection" ] || exit 0
name="${selection#* }"
nmcli connection up "$name" && notify "Verbindung $name gestartet."
}
disconnect_network() {
local conn
conn="$(active_connection)"
if [ -z "$conn" ]; then
notify "Keine aktive Netzwerkverbindung gefunden."
exit 0
fi
nmcli connection down "$conn" && notify "Verbindung $conn getrennt."
}
require_nmcli
current="$(active_connection)"
[ -n "$current" ] || current="Nicht verbunden"
choice="$(
printf '%s\n' \
"󰤨 WLAN verbinden" \
"󰖩 WLAN ein/aus" \
"󰛳 Gespeicherte Verbindungen" \
"󰅖 Aktive Verbindung trennen" \
"󰑓 Netzwerk neu scannen" \
"󰩟 Status: $current" |
wofi_pick "󰤨 Netzwerk"
)"
case "$choice" in
*"WLAN verbinden"*)
connect_wifi
;;
*"WLAN ein/aus"*)
if [ "$(wifi_status)" = "enabled" ]; then
nmcli radio wifi off && notify "WLAN ausgeschaltet."
else
nmcli radio wifi on && notify "WLAN eingeschaltet."
fi
;;
*"Gespeicherte Verbindungen"*)
saved_connections
;;
*"Aktive Verbindung trennen"*)
disconnect_network
;;
*"Netzwerk neu scannen"*)
nmcli device wifi rescan && notify "WLAN-Scan gestartet."
;;
*"Status:"*)
nmcli general status | wofi --dmenu --prompt "󰩟 Netzwerkstatus"
;;
esac

211
config/hypr/Scripts/power-menu.py Executable file
View File

@@ -0,0 +1,211 @@
#!/usr/bin/env python3
import os
import shlex
import subprocess
from pathlib import Path
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Gdk, Gtk # noqa: E402
HYPR_DIR = Path.home() / ".config" / "hypr"
THEME_DIR = HYPR_DIR / "Themes"
CURRENT_WALLPAPER = HYPR_DIR / "current-wallpaper"
DEFAULT_THEME = {
"NAME": "Power",
"ACCENT": "#f38ba8",
"ACCENT_2": "#cba6f7",
"BACKGROUND_HEX": "#18141f",
"PANEL_HEX": "#313244",
"FOREGROUND": "#f5e0dc",
"MUTED": "#a6adc8",
"SELECTED_TEXT": "#11111b",
}
def parse_theme_file(path):
theme = {}
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, value = line.split("=", 1)
key = key.strip()
value = value.strip()
try:
parsed = shlex.split(value)
theme[key] = parsed[0] if parsed else ""
except ValueError:
theme[key] = value.strip("\"'")
return theme
def load_current_theme():
current_wallpaper = ""
if CURRENT_WALLPAPER.exists():
current_wallpaper = CURRENT_WALLPAPER.read_text(encoding="utf-8", errors="ignore").strip()
for theme_file in sorted(THEME_DIR.glob("*.theme")):
theme = parse_theme_file(theme_file)
if theme.get("WALLPAPER") == current_wallpaper:
return {**DEFAULT_THEME, **theme}
rose = THEME_DIR / "rose-night.theme"
if rose.exists():
return {**DEFAULT_THEME, **parse_theme_file(rose)}
return DEFAULT_THEME
def run(command):
subprocess.Popen(command, start_new_session=True)
Gtk.main_quit()
class PowerMenu(Gtk.Window):
def __init__(self):
super().__init__(title="Power")
self.theme = load_current_theme()
self.set_decorated(False)
self.set_resizable(False)
self.set_keep_above(True)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
self.set_skip_taskbar_hint(True)
self.set_skip_pager_hint(True)
self.set_app_paintable(True)
self.connect("key-press-event", self.on_key_press)
self.connect("focus-out-event", lambda *_: Gtk.main_quit())
overlay = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=18)
overlay.get_style_context().add_class("power-shell")
title = Gtk.Label(label=self.theme.get("NAME", "Power"))
title.get_style_context().add_class("power-title")
overlay.pack_start(title, False, False, 0)
grid = Gtk.Grid()
grid.set_column_spacing(14)
grid.set_row_spacing(14)
grid.set_halign(Gtk.Align.CENTER)
actions = [
("", "Sperren", ["hyprlock"]),
("󰤄", "Ruhezustand", ["systemctl", "suspend"]),
("󰗽", "Abmelden", ["hyprctl", "dispatch", "exit"]),
("󰜉", "Neustart", ["systemctl", "reboot"]),
("", "Ausschalten", ["systemctl", "poweroff"]),
("󰗼", "Abbrechen", None),
]
for index, (icon, label, command) in enumerate(actions):
button = self.make_button(icon, label, command)
grid.attach(button, index % 3, index // 3, 1, 1)
overlay.pack_start(grid, False, False, 0)
self.add(overlay)
self.apply_css()
def make_button(self, icon, label, command):
button = Gtk.Button()
button.get_style_context().add_class("power-button")
button.set_size_request(150, 118)
button.set_relief(Gtk.ReliefStyle.NONE)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
box.set_halign(Gtk.Align.CENTER)
box.set_valign(Gtk.Align.CENTER)
icon_label = Gtk.Label(label=icon)
icon_label.get_style_context().add_class("power-icon")
text_label = Gtk.Label(label=label)
text_label.get_style_context().add_class("power-label")
box.pack_start(icon_label, False, False, 0)
box.pack_start(text_label, False, False, 0)
button.add(box)
if command:
button.connect("clicked", lambda *_: run(command))
else:
button.connect("clicked", lambda *_: Gtk.main_quit())
return button
def apply_css(self):
css = f"""
window {{
background: transparent;
}}
.power-shell {{
margin: 0;
padding: 24px;
border-radius: 26px;
border: 1px solid {self.theme["ACCENT"]};
background: alpha({self.theme["BACKGROUND_HEX"]}, 0.94);
box-shadow: 0 22px 70px alpha(#000000, 0.55);
}}
.power-title {{
color: {self.theme["FOREGROUND"]};
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif;
font-size: 18px;
font-weight: 800;
}}
.power-button {{
color: {self.theme["FOREGROUND"]};
background: alpha({self.theme["PANEL_HEX"]}, 0.82);
border: 1px solid alpha({self.theme["ACCENT_2"]}, 0.42);
border-radius: 18px;
transition: 160ms ease;
}}
.power-button:hover,
.power-button:focus {{
color: {self.theme["SELECTED_TEXT"]};
background: linear-gradient(135deg, {self.theme["ACCENT"]}, {self.theme["ACCENT_2"]});
border-color: {self.theme["ACCENT"]};
}}
.power-icon {{
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif;
font-size: 34px;
font-weight: 800;
}}
.power-label {{
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif;
font-size: 13px;
font-weight: 800;
}}
"""
provider = Gtk.CssProvider()
provider.load_from_data(css.encode("utf-8"))
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
)
def on_key_press(self, _widget, event):
if event.keyval in (Gdk.KEY_Escape, Gdk.KEY_q):
Gtk.main_quit()
if __name__ == "__main__":
window = PowerMenu()
window.show_all()
Gtk.main()

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env bash
set -euo pipefail
screenshot_dir="${XDG_PICTURES_DIR:-$HOME/Pictures}/Screenshots"
notify() {
notify-send "󰸉 Screenshot" "$1"
}
require_cmd() {
local cmd="$1"
if ! command -v "$cmd" >/dev/null 2>&1; then
notify "$cmd ist nicht installiert."
exit 1
fi
}
filename() {
date +%Y-%m-%d_%H-%M-%S.png
}
take_hyprshot() {
local mode="$1"
local name="$2"
require_cmd hyprshot
mkdir -p "$screenshot_dir"
hyprshot -m "$mode" -o "$screenshot_dir" -f "$name" -s
printf '%s/%s\n' "$screenshot_dir" "$name"
}
annotate_region() {
local name path
require_cmd satty
name="$(filename)"
path="$(take_hyprshot region "$name")"
satty --filename "$path" --output-filename "$path"
}
quick_region() {
local name
name="$(filename)"
take_hyprshot region "$name" >/dev/null
notify "Bereich gespeichert."
}
quick_window() {
local name
name="$(filename)"
take_hyprshot window "$name" >/dev/null
notify "Fenster gespeichert."
}
quick_output() {
local name
name="$(filename)"
take_hyprshot output "$name" >/dev/null
notify "Bildschirm gespeichert."
}
copy_region() {
require_cmd hyprshot
hyprshot -m region --clipboard-only -s
notify "Bereich in die Zwischenablage kopiert."
}
show_menu() {
require_cmd wofi
local choice
choice="$(
printf '%s\n' \
"󰹑 Bereich markieren" \
"󰸉 Bereich speichern" \
"󰍹 Fenster speichern" \
"󰹑 Bildschirm speichern" \
"󰅌 Bereich kopieren" |
wofi --dmenu --prompt "󰸉 Screenshot" --insensitive
)"
case "$choice" in
*"Bereich markieren"*)
annotate_region
;;
*"Bereich speichern"*)
quick_region
;;
*"Fenster speichern"*)
quick_window
;;
*"Bildschirm speichern"*)
quick_output
;;
*"Bereich kopieren"*)
copy_region
;;
esac
}
case "${1:-menu}" in
annotate-region)
annotate_region
;;
region)
quick_region
;;
window)
quick_window
;;
output)
quick_output
;;
copy-region)
copy_region
;;
menu)
show_menu
;;
*)
notify "Unbekannter Screenshot-Modus: $1"
exit 2
;;
esac

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
choice="$(
printf '%s\n' \
"󰉼 Aussehen" \
"󰕾 Audio" \
"󰂯 Bluetooth" \
"󰍹 Display" \
"󰤨 Netzwerk" \
"󰸉 Screenshot" \
"󰒓 System" |
wofi --dmenu --prompt "󰒓 Einstellungen" --insensitive
)"
case "$choice" in
*"Aussehen"*)
"$SCRIPT_DIR/appearance-menu.sh"
;;
*"Audio"*)
"$SCRIPT_DIR/audio-menu.sh"
;;
*"Netzwerk"*)
"$SCRIPT_DIR/network-menu.sh"
;;
*"Bluetooth"*)
"$SCRIPT_DIR/bluetooth-menu.sh"
;;
*"Display"*)
"$SCRIPT_DIR/display-menu.sh"
;;
*"Screenshot"*)
"$SCRIPT_DIR/screenshot-menu.sh"
;;
*"System"*)
"$SCRIPT_DIR/system-menu.sh"
;;
esac

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail
notify() {
notify-send "󰒓 System" "$1"
}
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
start_detached() {
local command_name="$1"
local unit_name="${command_name//[^[:alnum:]_.-]/-}"
if command -v systemd-run >/dev/null 2>&1; then
systemd-run --user --unit="$unit_name" --collect "$@" >/dev/null 2>&1 && return
fi
if command -v setsid >/dev/null 2>&1; then
setsid -f "$@" >/dev/null 2>&1
else
"$@" >/dev/null 2>&1 &
fi
}
restart_waybar() {
local i
pkill -x waybar >/dev/null 2>&1 || true
for i in {1..20}; do
pgrep -x waybar >/dev/null 2>&1 || break
sleep 0.05
done
pgrep -x waybar >/dev/null 2>&1 && pkill -9 -x waybar >/dev/null 2>&1 || true
start_detached waybar
}
choice="$(
printf '%s\n' \
"󰑓 Hyprland neu laden" \
"󰌢 Waybar neu starten" \
"󰗽 Bildschirm heller" \
"󰗾 Bildschirm dunkler" \
"󰍃 Session beenden" |
wofi --dmenu --prompt "󰒓 System" --insensitive
)"
case "$choice" in
*"Hyprland neu laden"*)
hyprctl reload
;;
*"Waybar neu starten"*)
restart_waybar
;;
*"Bildschirm heller"*)
if command -v brightnessctl >/dev/null 2>&1; then
brightnessctl -e4 -n2 set 5%+
else
notify "brightnessctl ist nicht installiert."
fi
;;
*"Bildschirm dunkler"*)
if command -v brightnessctl >/dev/null 2>&1; then
brightnessctl -e4 -n2 set 5%-
else
notify "brightnessctl ist nicht installiert."
fi
;;
*"Session beenden"*)
if command -v hyprshutdown >/dev/null 2>&1; then
hyprshutdown
else
hyprctl dispatch exit
fi
;;
esac

1204
config/hypr/Scripts/theme-menu.sh Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
if pgrep -x wofi >/dev/null; then
pkill -x wofi
else
wofi --show drun
fi

View File

@@ -0,0 +1,197 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
HYPR_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
WALLPAPER_DIR="${WALLPAPER_DIR:-$HOME/Bilder/Wallpaper}"
HYPRPAPER_CONF="$HYPR_DIR/hyprpaper.conf"
CURRENT_WALLPAPER="$HYPR_DIR/current-wallpaper"
TRANSITION_TYPE="${TRANSITION_TYPE:-grow}"
TRANSITION_DURATION="${TRANSITION_DURATION:-1.0}"
TRANSITION_FPS="${TRANSITION_FPS:-60}"
TRANSITION_POS="${TRANSITION_POS:-center}"
notify() {
notify-send "󰸉 Wallpaper" "$1" >/dev/null 2>&1 || true
}
start_detached() {
local command_name="$1"
local unit_name="${command_name//[^[:alnum:]_.-]/-}"
if command -v systemd-run >/dev/null 2>&1; then
systemd-run --user --unit="$unit_name" --collect "$@" >/dev/null 2>&1 && return
fi
if command -v setsid >/dev/null 2>&1; then
setsid -f "$@" >/dev/null 2>&1
else
"$@" >/dev/null 2>&1 &
fi
}
apply_wallpaper() {
local wallpaper="$1"
cat >"$HYPRPAPER_CONF" <<EOF
wallpaper {
monitor =
path = $wallpaper
fit_mode = cover
}
EOF
printf '%s\n' "$wallpaper" >"$CURRENT_WALLPAPER"
if command -v awww >/dev/null 2>&1; then
if ! pgrep -x awww-daemon >/dev/null 2>&1; then
start_detached awww-daemon
sleep 0.2
fi
awww img "$wallpaper" \
--transition-type "$TRANSITION_TYPE" \
--transition-duration "$TRANSITION_DURATION" \
--transition-fps "$TRANSITION_FPS" \
--transition-pos "$TRANSITION_POS" >/dev/null 2>&1 || true
return
fi
if command -v swww >/dev/null 2>&1; then
if ! pgrep -x swww-daemon >/dev/null 2>&1; then
start_detached swww-daemon
sleep 0.2
fi
swww img "$wallpaper" \
--transition-type "$TRANSITION_TYPE" \
--transition-duration "$TRANSITION_DURATION" \
--transition-fps "$TRANSITION_FPS" \
--transition-pos "$TRANSITION_POS" >/dev/null 2>&1 || true
return
fi
if command -v hyprctl >/dev/null 2>&1; then
if ! pgrep -x hyprpaper >/dev/null 2>&1; then
start_detached hyprpaper
sleep 0.2
fi
hyprctl hyprpaper preload "$wallpaper" >/dev/null || true
hyprctl hyprpaper wallpaper ",$wallpaper" >/dev/null || true
fi
}
preview_wallpaper() {
local wallpaper="$1"
if command -v swayimg >/dev/null 2>&1; then
start_detached swayimg "$wallpaper"
return
fi
if command -v imv >/dev/null 2>&1; then
start_detached imv "$wallpaper"
return
fi
if command -v kitty >/dev/null 2>&1; then
start_detached kitty \
--class wallpaper-preview \
--title "Wallpaper Vorschau" \
sh -c 'clear; printf "%s\n\n" "$1"; kitty +kitten icat --fit both --align center "$1"; printf "\nEnter schliesst die Vorschau."; read -r _' \
sh "$wallpaper"
return
fi
notify "Kein Vorschauprogramm gefunden."
}
if [[ "${1:-}" == "--apply" ]]; then
wallpaper="${2:-}"
if [[ -z "$wallpaper" || ! -f "$wallpaper" ]]; then
notify "Wallpaper nicht gefunden."
exit 1
fi
apply_wallpaper "$wallpaper"
notify "$(basename "$wallpaper") angewendet."
exit 0
fi
if [[ "${1:-}" == "--preview" ]]; then
wallpaper="${2:-}"
if [[ -z "$wallpaper" || ! -f "$wallpaper" ]]; then
notify "Wallpaper nicht gefunden."
exit 1
fi
preview_wallpaper "$wallpaper"
exit 0
fi
pick_wallpaper() {
local entries=()
local wallpaper
mapfile -t wallpapers < <(
find "$WALLPAPER_DIR" -maxdepth 1 -type f \
\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' -o -iname '*.gif' \) |
sort
)
if ((${#wallpapers[@]} == 0)); then
notify "Keine Bilder in $WALLPAPER_DIR gefunden."
exit 1
fi
for wallpaper in "${wallpapers[@]}"; do
entries+=("󰸉 $(basename "$wallpaper")")
done
choice="$(
printf '%s\n' "${entries[@]}" |
wofi --dmenu --prompt "󰸉 Wallpaper" --insensitive
)"
[[ -z "${choice:-}" ]] && exit 0
for i in "${!entries[@]}"; do
if [[ "$choice" == "${entries[$i]}" ]]; then
printf '%s\n' "${wallpapers[$i]}"
return
fi
done
}
while true; do
wallpaper="$(pick_wallpaper)"
[[ -z "${wallpaper:-}" ]] && exit 0
action="$(
printf '%s\n' \
"󰋩 Vorschau" \
"󰄬 Anwenden" \
"󰁍 Zurueck" |
wofi --dmenu --prompt "󰸉 $(basename "$wallpaper")" --insensitive
)"
case "$action" in
*"Vorschau"*)
preview_wallpaper "$wallpaper"
;;
*"Anwenden"*)
apply_wallpaper "$wallpaper"
notify "$(basename "$wallpaper") angewendet."
exit 0
;;
*"Zurueck"*)
;;
*)
exit 0
;;
esac
done

View File

@@ -0,0 +1,34 @@
NAME="Forest Neon"
ICON="󰌪"
WALLPAPER="/home/pascal/Bilder/Wallpaper/forest.jpg"
TRANSITION_TYPE="grow"
TRANSITION_DURATION="1.0"
TRANSITION_FPS="60"
TRANSITION_POS="center"
ACTIVE_BORDER="rgba(00ff9cee) rgba(00cc88ee) 45deg"
INACTIVE_BORDER="rgba(2a2a2aaa)"
ACCENT="#00ff9c"
ACCENT_2="#00cc88"
BACKGROUND="rgba(20, 20, 30, 0.95)"
BACKGROUND_SOFT="rgba(40, 40, 55, 0.8)"
BACKGROUND_HEX="#14141e"
PANEL_HEX="#282837"
FOREGROUND="#cdd6f4"
MUTED="#cccccc"
SELECTED_TEXT="#000000"
WAYBAR_PANEL="rgba(24, 24, 37, 0.72)"
WAYBAR_ISLAND="rgba(5, 5, 9, 0.88)"
WAYBAR_MUTED="#7f849c"
WAYBAR_SUCCESS="#a6e3a1"
WAYBAR_WARNING="#f9e2af"
WAYBAR_DANGER="#f38ba8"
WAYBAR_BLUE="#89b4fa"
WAYBAR_CYAN="#89dceb"
WAYBAR_ORANGE="#fab387"
WAYBAR_PURPLE="#cba6f7"
APP_THEME_MODE="prefer-dark"
GNOME_ACCENT_COLOR="green"
GTK_THEME_NAME="Adwaita"
ICON_THEME_NAME="Papirus-ForestNeon"
PAPIRUS_FOLDER_COLOR="green"
KDE_COLOR_SCHEME="ForestNeon"

View File

@@ -0,0 +1,34 @@
NAME="Rose Night"
ICON="󰌪"
WALLPAPER="/home/pascal/Bilder/Wallpaper/rose-pink.jpg"
TRANSITION_TYPE="wipe"
TRANSITION_DURATION="1.0"
TRANSITION_FPS="60"
TRANSITION_POS="center"
ACTIVE_BORDER="rgba(f38ba8ee) rgba(cba6f7ee) 45deg"
INACTIVE_BORDER="rgba(313244aa)"
ACCENT="#f38ba8"
ACCENT_2="#cba6f7"
BACKGROUND="rgba(24, 20, 31, 0.95)"
BACKGROUND_SOFT="rgba(49, 50, 68, 0.82)"
BACKGROUND_HEX="#18141f"
PANEL_HEX="#313244"
FOREGROUND="#f5e0dc"
MUTED="#cdd6f4"
SELECTED_TEXT="#11111b"
WAYBAR_PANEL="rgba(49, 50, 68, 0.76)"
WAYBAR_ISLAND="rgba(24, 20, 31, 0.90)"
WAYBAR_MUTED="#a6adc8"
WAYBAR_SUCCESS="#f5c2e7"
WAYBAR_WARNING="#f9e2af"
WAYBAR_DANGER="#f38ba8"
WAYBAR_BLUE="#cba6f7"
WAYBAR_CYAN="#f5c2e7"
WAYBAR_ORANGE="#fab387"
WAYBAR_PURPLE="#cba6f7"
APP_THEME_MODE="prefer-dark"
GNOME_ACCENT_COLOR="pink"
GTK_THEME_NAME="Adwaita"
ICON_THEME_NAME="Papirus-RoseNight"
PAPIRUS_FOLDER_COLOR="pink"
KDE_COLOR_SCHEME="RoseNight"

312
config/hypr/ags/homelab.css Normal file
View File

@@ -0,0 +1,312 @@
* {
all: unset;
font-family: "JetBrainsMono Nerd Font", "Noto Sans", sans-serif;
font-size: 13px;
}
.homelab-window {
background: transparent;
}
.login-panel,
.shell {
border: 1px solid rgba(205, 214, 244, 0.16);
border-radius: 16px;
background: rgba(20, 20, 30, 0.97);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
color: #cdd6f4;
}
.login-panel {
min-width: 520px;
padding: 18px;
}
.shell {
min-width: 0;
min-height: 0;
}
.sidebar {
min-width: 218px;
padding: 14px 10px;
border-right: 1px solid rgba(205, 214, 244, 0.10);
background: rgba(14, 16, 24, 0.72);
}
.brand {
padding: 8px 8px 14px;
}
.brand-title {
color: #00ff9c;
font-size: 19px;
font-weight: 800;
}
.brand-subtitle,
.subtitle,
.row-subtitle {
color: #a6adc8;
font-size: 12px;
}
.nav-button {
min-height: 34px;
padding: 7px 8px;
border-radius: 8px;
color: #cdd6f4;
}
.nav-button:hover,
.nav-button:focus {
background: rgba(0, 255, 156, 0.14);
}
.nav-button.active {
background: rgba(0, 255, 156, 0.22);
color: #00ff9c;
}
.nav-icon {
min-width: 24px;
}
.nav-label {
font-size: 12px;
}
.content {
padding: 18px;
}
.page-scroll {
min-width: 0;
min-height: 0;
}
.header {
min-height: 38px;
}
.title {
font-size: 20px;
font-weight: 750;
color: #00ff9c;
}
.subtitle {
margin-top: 3px;
}
entry {
padding: 10px 12px;
border: 1px solid rgba(205, 214, 244, 0.14);
border-radius: 10px;
background: rgba(40, 40, 55, 0.82);
color: #cdd6f4;
}
entry:focus {
border-color: rgba(0, 255, 156, 0.78);
}
.grid,
.live-strip,
.service-grid {
min-height: 92px;
}
.card,
.chart-card,
.service-card,
.table-row {
border: 1px solid rgba(205, 214, 244, 0.10);
border-radius: 8px;
background: rgba(40, 40, 55, 0.62);
}
.card {
min-width: 220px;
padding: 12px;
}
.wide-card {
min-width: 0;
min-height: 220px;
}
.log-card {
min-width: 0;
min-height: 560px;
}
.stat-card {
min-height: 80px;
}
.chart-card {
min-width: 260px;
min-height: 98px;
padding: 12px;
}
.chart-card.cpu {
border-color: rgba(0, 255, 156, 0.22);
}
.chart-card.memory {
border-color: rgba(137, 180, 250, 0.26);
}
.chart-card.load {
border-color: rgba(249, 226, 175, 0.24);
}
.chart-card.docker,
.chart-card.network {
border-color: rgba(116, 199, 236, 0.24);
}
.card-title {
color: #00cc88;
font-size: 12px;
font-weight: 750;
}
.card-value {
color: #cdd6f4;
}
.compact,
.row-subtitle {
font-size: 12px;
}
.percent-label,
.chart-value {
color: #f9e2af;
font-size: 12px;
font-weight: 750;
}
.sparkline {
color: #00ff9c;
font-size: 25px;
letter-spacing: 0;
}
.meter {
min-height: 8px;
border-radius: 6px;
background: rgba(205, 214, 244, 0.10);
}
.meter-fill {
min-height: 8px;
border-radius: 6px;
background: linear-gradient(to right, #00ff9c, #89b4fa);
}
.alerts {
min-height: 32px;
}
.alert,
.pill {
padding: 4px 8px;
border-radius: 999px;
background: rgba(205, 214, 244, 0.10);
color: #cdd6f4;
font-size: 11px;
}
.alert.ok,
.pill.ok {
background: rgba(0, 255, 156, 0.16);
color: #00ff9c;
}
.alert.warn,
.pill.warn {
background: rgba(249, 226, 175, 0.16);
color: #f9e2af;
}
.table-row {
min-height: 72px;
padding: 10px;
}
.container-main {
min-width: 260px;
}
.container-stats {
min-width: 112px;
}
.row-actions {
min-width: 250px;
}
.row-title {
color: #cdd6f4;
font-weight: 750;
}
.service-card {
min-width: 250px;
min-height: 112px;
padding: 12px;
}
.actions {
margin-top: 2px;
}
.button,
.mini-button,
.icon-button {
padding: 8px 10px;
border-radius: 8px;
background: rgba(40, 40, 55, 0.82);
color: #cdd6f4;
}
.mini-button {
padding: 6px 8px;
font-size: 12px;
}
.button:hover,
.button:focus,
.mini-button:hover,
.mini-button:focus,
.icon-button:hover,
.icon-button:focus {
background: rgba(0, 255, 156, 0.22);
}
.primary {
background: rgba(0, 255, 156, 0.24);
color: #00ff9c;
}
.danger {
color: #f38ba8;
}
.close {
color: #f38ba8;
}
.icon-button {
min-width: 34px;
min-height: 34px;
padding: 0;
}
.error {
color: #f38ba8;
}

1124
config/hypr/ags/homelab.tsx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,115 @@
* {
all: unset;
font-family: "JetBrainsMono Nerd Font", "Noto Sans", sans-serif;
font-size: 14px;
}
.switcher-window {
background: transparent;
}
.switcher {
min-width: 680px;
padding: 18px;
border: 1px solid alpha(@ags_fg, 0.18);
border-radius: 16px;
background: @ags_bg;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
color: @ags_fg;
}
.header {
min-height: 34px;
}
.title {
font-size: 20px;
font-weight: 700;
}
.list-scroll {
min-height: 130px;
}
.item {
padding: 10px;
border: 1px solid alpha(@ags_fg, 0.10);
border-radius: 10px;
background: alpha(@ags_panel, 0.54);
}
.item-row {
min-height: 78px;
}
.item:hover,
.item:focus {
border-color: alpha(@ags_accent, 0.72);
background: alpha(@ags_panel, 0.82);
}
.item.active {
border-color: alpha(@ags_accent_2, 0.85);
}
.preview {
min-width: 96px;
min-height: 56px;
border-radius: 8px;
background-color: alpha(@ags_panel, 0.80);
background-size: cover;
background-position: center;
}
.preview-empty {
color: @ags_accent_2;
}
.item-title {
font-size: 15px;
font-weight: 700;
}
.item-subtitle {
margin-top: 4px;
color: @ags_muted;
font-size: 12px;
}
.swatches {
min-width: 86px;
}
.swatch {
min-width: 18px;
min-height: 18px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.16);
}
.icon-button {
min-width: 34px;
min-height: 34px;
border-radius: 8px;
color: @ags_fg;
background: alpha(@ags_panel, 0.62);
}
.preview-button {
min-width: 44px;
min-height: 78px;
}
.icon-button:hover,
.icon-button:focus {
background: alpha(@ags_accent, 0.28);
}
.close {
color: @ags_accent;
}
.empty {
min-height: 150px;
color: @ags_muted;
}

View File

@@ -0,0 +1,321 @@
import app from "ags/gtk4/app";
import { Astal, Gtk } from "ags/gtk4";
import { readFile } from "ags/file";
import { execAsync } from "ags/process";
import css from "./switcher.css";
import GLib from "gi://GLib";
type SwitcherItem = {
type: "theme" | "wallpaper";
path: string;
name: string;
icon?: string;
wallpaper: string;
active?: boolean;
accent?: string;
accent2?: string;
muted?: string;
};
const HYPR_DIR = GLib.getenv("HYPR_DIR") || "/home/pascal/.config/hypr";
const SCRIPT_DIR = `${HYPR_DIR}/Scripts`;
const THEME_DIR = GLib.getenv("HYPR_SWITCHER_THEME_DIR") || `${HYPR_DIR}/Themes`;
const WALLPAPER_DIR = GLib.getenv("HYPR_SWITCHER_WALLPAPER_DIR")
|| GLib.getenv("WALLPAPER_DIR")
|| `${GLib.get_home_dir()}/Bilder/Wallpaper`;
const CURRENT_WALLPAPER = `${HYPR_DIR}/current-wallpaper`;
const IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".gif"];
type UiTheme = {
accent: string;
accent2: string;
background: string;
backgroundSoft: string;
foreground: string;
muted: string;
panelHex: string;
};
function notify(message: string) {
execAsync(["notify-send", "AGS Switcher", message]).catch(console.error);
}
function fileExists(path: string) {
return GLib.file_test(path, GLib.FileTest.EXISTS);
}
function readText(path: string) {
try {
return readFile(path);
} catch {
return "";
}
}
function listFiles(dir: string, predicate: (path: string, name: string) => boolean) {
try {
const directory = GLib.Dir.open(dir, 0);
const files: string[] = [];
let name = directory.read_name();
while (name !== null) {
const path = `${dir}/${name}`;
if (predicate(path, name)) {
files.push(path);
}
name = directory.read_name();
}
return files.sort((a, b) => a.localeCompare(b));
} catch {
return [];
}
}
function shellValue(contents: string, key: string) {
const regex = new RegExp(`^${key}=(["']?)(.*?)\\1$`, "m");
return contents.match(regex)?.[2] || "";
}
function activeTheme(): UiTheme {
const activeWallpaper = currentWallpaper();
const fallback = {
accent: "#00ff9c",
accent2: "#00cc88",
background: "rgba(20, 20, 30, 0.95)",
backgroundSoft: "rgba(40, 40, 55, 0.8)",
foreground: "#cdd6f4",
muted: "#cccccc",
panelHex: "#282837",
};
const themeFile = listFiles(THEME_DIR, (_path, name) => name.endsWith(".theme"))
.find(path => shellValue(readText(path), "WALLPAPER") === activeWallpaper);
if (!themeFile) {
return fallback;
}
const contents = readText(themeFile);
return {
accent: shellValue(contents, "ACCENT") || fallback.accent,
accent2: shellValue(contents, "ACCENT_2") || fallback.accent2,
background: shellValue(contents, "BACKGROUND") || fallback.background,
backgroundSoft: shellValue(contents, "BACKGROUND_SOFT") || fallback.backgroundSoft,
foreground: shellValue(contents, "FOREGROUND") || fallback.foreground,
muted: shellValue(contents, "MUTED") || fallback.muted,
panelHex: shellValue(contents, "PANEL_HEX") || fallback.panelHex,
};
}
function themeCss(theme: UiTheme) {
return [
`@define-color ags_accent ${theme.accent};`,
`@define-color ags_accent_2 ${theme.accent2};`,
`@define-color ags_bg ${theme.background};`,
`@define-color ags_bg_soft ${theme.backgroundSoft};`,
`@define-color ags_fg ${theme.foreground};`,
`@define-color ags_muted ${theme.muted};`,
`@define-color ags_panel ${theme.panelHex};`,
css,
].join("\n");
}
function basename(path: string) {
return GLib.path_get_basename(path);
}
function currentWallpaper() {
return readText(CURRENT_WALLPAPER).trim();
}
function loadThemes(): SwitcherItem[] {
return listFiles(THEME_DIR, (_path, name) => name.endsWith(".theme"))
.map(path => {
const contents = readText(path);
return {
type: "theme",
path,
name: shellValue(contents, "NAME") || basename(path).replace(/\.theme$/, ""),
icon: shellValue(contents, "ICON") || "󰌪",
wallpaper: shellValue(contents, "WALLPAPER"),
accent: shellValue(contents, "ACCENT") || "#f38ba8",
accent2: shellValue(contents, "ACCENT_2") || "#cba6f7",
muted: shellValue(contents, "MUTED") || "#cdd6f4",
};
});
}
function loadWallpapers(): SwitcherItem[] {
const active = currentWallpaper();
return listFiles(WALLPAPER_DIR, (_path, name) =>
IMAGE_EXTENSIONS.some(ext => name.toLowerCase().endsWith(ext)),
).map(path => ({
type: "wallpaper",
path,
name: basename(path),
wallpaper: path,
active: path === active,
}));
}
function applyItem(item: SwitcherItem) {
const command = item.type === "theme"
? [`${SCRIPT_DIR}/theme-menu.sh`, "--apply", item.path]
: [`${SCRIPT_DIR}/wallpaper-menu.sh`, "--apply", item.path];
execAsync(command)
.then(() => app.quit())
.catch(error => {
console.error(error);
notify(`${item.name} konnte nicht angewendet werden.`);
});
}
function previewWallpaper(item: SwitcherItem) {
execAsync([`${SCRIPT_DIR}/wallpaper-menu.sh`, "--preview", item.path]).catch(error => {
console.error(error);
notify(`${item.name} konnte nicht geoeffnet werden.`);
});
}
function Preview({ item }: { item: SwitcherItem }) {
const hasImage = item.wallpaper && fileExists(item.wallpaper);
return (
<box class={hasImage ? "preview" : "preview preview-empty"}>
{hasImage
? <image class="preview-image" file={item.wallpaper} pixelSize={92} />
: <label label="󰌪" />}
</box>
);
}
function Swatches({ item }: { item: SwitcherItem }) {
return (
<box class="swatches" spacing={6}>
{[item.accent, item.accent2, item.muted].map(color => (
<box class="swatch" css={`background: ${color};`} />
))}
</box>
);
}
function ItemButton({ item }: { item: SwitcherItem }) {
const content = (
<box spacing={14}>
<Preview item={item} />
<box
orientation={Gtk.Orientation.VERTICAL}
hexpand
valign={Gtk.Align.CENTER}
>
<label
class="item-title"
xalign={0}
ellipsize={3}
label={`${item.icon || "󰸉"} ${item.name}`}
/>
<label
class="item-subtitle"
xalign={0}
ellipsize={3}
label={item.type === "theme"
? basename(item.path)
: item.active ? "Aktuelles Wallpaper" : item.path}
/>
</box>
{item.type === "theme" ? <Swatches item={item} /> : <box />}
</box>
);
const applyButton = (
<button
class={`item ${item.active ? "active" : ""}`}
hexpand
onClicked={() => applyItem(item)}
>
{content}
</button>
);
if (item.type === "theme") {
return applyButton;
}
return (
<box class="item-row" spacing={8}>
{applyButton}
<button
class="icon-button preview-button"
tooltipText="Vorschau"
onClicked={() => previewWallpaper(item)}
>
<label label="󰋩" />
</button>
</box>
);
}
function SwitcherWindow(mode: string) {
const isTheme = mode === "theme" || mode === "themes";
const items = isTheme ? loadThemes() : loadWallpapers();
const title = isTheme ? "Theme wechseln" : "Wallpaper wechseln";
const empty = isTheme ? `Keine Themes in ${THEME_DIR}` : `Keine Bilder in ${WALLPAPER_DIR}`;
return (
<window
name="ags-hypr-switcher"
namespace="ags-hypr-switcher"
class="switcher-window"
visible
keymode={Astal.Keymode.EXCLUSIVE}
anchor={Astal.WindowAnchor.TOP}
application={app}
>
<box
class="switcher"
orientation={Gtk.Orientation.VERTICAL}
spacing={16}
marginTop={70}
>
<box class="header">
<label class="title" hexpand xalign={0} label={title} />
<button
class="icon-button close"
tooltipText="Schliessen"
onClicked={() => app.quit()}
>
<label label="" />
</button>
</box>
{items.length > 0
? (
<scrolledwindow
class="list-scroll"
hscrollbarPolicy={Gtk.PolicyType.NEVER}
maxContentHeight={600}
>
<box orientation={Gtk.Orientation.VERTICAL} spacing={10}>
{items.map(item => <ItemButton item={item} />)}
</box>
</scrolledwindow>
)
: (
<box class="empty">
<label label={empty} />
</box>
)}
</box>
</window>
);
}
app.start({
css: themeCss(activeTheme()),
instanceName: "hypr-switcher",
main(mode = "wallpaper") {
SwitcherWindow(mode);
},
});

View File

@@ -0,0 +1,5 @@
# Generated by ~/.config/hypr/Scripts/theme-menu.sh
general {
col.active_border = rgba(00ff9cee) rgba(00cc88ee) 45deg
col.inactive_border = rgba(2a2a2aaa)
}

View File

@@ -0,0 +1 @@
/home/pascal/Bilder/Wallpaper/forest.jpg

308
config/hypr/hyprland.conf Normal file
View File

@@ -0,0 +1,308 @@
# #######################################################################################
# AUTOGENERATED HYPRLAND CONFIG.
# EDIT THIS CONFIG ACCORDING TO THE WIKI INSTRUCTIONS.
# #######################################################################################
# This is an example Hyprland config file.
# Refer to the wiki for more information.
# https://wiki.hypr.land/Configuring/
# Please note not all available settings / options are set here.
# For a full list, see the wiki
# You can split this configuration into multiple files
# Create your files separately and then link them to this file like this:
# source = ~/.config/hypr/myColors.conf
################
### MONITORS ###
################
# See https://wiki.hypr.land/Configuring/Monitors/
monitor=,preferred,auto,1
###################
### MY PROGRAMS ###
###################
# See https://wiki.hypr.land/Configuring/Keywords/
# Set programs that you use
$terminal = kitty
$fileManager = nautilus
$menu = wofi
#################
### AUTOSTART ###
#################
# Autostart necessary processes (like notifications daemons, status bars, etc.)
# Or execute your favorite apps at launch like this:
exec-once = sh -c 'if command -v awww-daemon >/dev/null 2>&1; then awww-daemon; elif command -v swww-daemon >/dev/null 2>&1; then swww-daemon; else hyprpaper; fi'
exec-once = waybar
exec-once = swaync
#############################
### ENVIRONMENT VARIABLES ###
#############################
# See https://wiki.hypr.land/Configuring/Environment-variables/
env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24
env = QT_QPA_PLATFORMTHEME,qt6ct
env = QT_STYLE_OVERRIDE,Fusion
# https://wiki.hypr.land/Configuring/Variables/#general
general {
gaps_in = 6
gaps_out = 25
border_size = 2
col.active_border = rgba(00ff9cee) rgba(00cc88ee) 45deg
col.inactive_border = rgba(2a2a2aaa)
resize_on_border = false
allow_tearing = false
layout = dwindle
}
source = ~/.config/hypr/current-theme.conf
# https://wiki.hypr.land/Configuring/Variables/#decoration
decoration {
rounding = 14
rounding_power = 3
active_opacity = 0.90
inactive_opacity = 0.75
shadow {
enabled = true
range = 20
render_power = 4
color = rgba(000000aa)
}
blur {
enabled = true
size = 6
passes = 2
vibrancy = 0.25
vibrancy_darkness = 0.3
}
}
# https://wiki.hypr.land/Configuring/Variables/#animations
animations {
enabled = yes
# Curves
bezier = smoothOut, 0.22, 1, 0.36, 1
bezier = smoothIn, 0.64, 0, 0.78, 0
bezier = smoothInOut, 0.83, 0, 0.17, 1
bezier = soft, 0.25, 0.1, 0.25, 1
bezier = quick, 0.3, 0, 0.1, 1
# Animations
animation = global, 1, 8, soft
animation = border, 1, 5, smoothOut
animation = fade, 1, 5, soft
animation = windows, 1, 6, smoothOut
animation = windowsIn, 1, 6, smoothOut, popin 85%
animation = windowsOut, 1, 5, smoothIn, popin 85%
animation = layers, 1, 5, smoothOut
animation = layersIn, 1, 5, smoothOut, fade
animation = layersOut, 1, 4, smoothIn, fade
animation = fadeIn, 1, 4, soft
animation = fadeOut, 1, 4, soft
animation = fadeLayersIn, 1, 4, soft
animation = fadeLayersOut, 1, 4, soft
animation = workspaces, 1, 5, smoothInOut, slide
animation = workspacesIn, 1, 5, smoothInOut, slide
animation = workspacesOut, 1, 5, smoothInOut, slide
animation = zoomFactor, 1, 6, quick
}
# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this
}
# See https://wiki.hypr.land/Configuring/Master-Layout/ for more
master {
new_status = master
}
# https://wiki.hypr.land/Configuring/Variables/#misc
misc {
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(
}
#############
### INPUT ###
#############
# https://wiki.hypr.land/Configuring/Variables/#input
input {
kb_layout = de
kb_variant =
kb_model =
kb_options =
kb_rules =
follow_mouse = 1
sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
touchpad {
natural_scroll = false
}
}
# See https://wiki.hypr.land/Configuring/Gestures
gesture = 3, horizontal, workspace
# Example per-device config
# See https://wiki.hypr.land/Configuring/Keywords/#per-device-input-configs for more
device {
name = epic-mouse-v1
sensitivity = -0.5
}
###################
### KEYBINDINGS ###
###################
# See https://wiki.hypr.land/Configuring/Keywords/
$mainMod = SUPER # Sets "Windows" key as main modifier
# Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more
bind = $mainMod, T, exec, $terminal
bind = $mainMod, Q, killactive,
bind = $mainMod, M, exec, ~/.config/hypr/Scripts/main-menu.sh
bind = $mainMod, E, exec, $fileManager
bind = $mainMod, N, exec, swaync-client -t
bind = $mainMod, V, togglefloating,
bind = $mainMod, R, exec, $menu
bind = $mainMod, W, exec, ~/.config/hypr/Scripts/ags-switcher.sh wallpaper
bind = $mainMod, P, exec, ~/.config/hypr/Scripts/power-menu.py
bind = $mainMod, L, exec, hyprlock
bind = $mainMod SHIFT, T, exec, ~/.config/hypr/Scripts/ags-switcher.sh theme
bind = $mainMod SHIFT, S, exec, ~/.config/hypr/Scripts/screenshot-menu.sh annotate-region
bind = $mainMod SHIFT, P, pseudo, # dwindle
bind = $mainMod, J, layoutmsg, togglesplit # dwindle
bind = $mainMod, SPACE, exec, ~/.config/hypr/Scripts/toggle-wofi.sh
# Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l
bind = $mainMod, right, movefocus, r
bind = $mainMod, up, movefocus, u
bind = $mainMod, down, movefocus, d
# Switch existing workspaces with CTRL + mainMod + arrow keys
bind = CTRL $mainMod, left, workspace, e-1
bind = CTRL $mainMod, right, workspace, e+1
# Switch workspaces with mainMod + [0-9]
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10
# Move active window to a workspace with mainMod + SHIFT + [0-9]
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
bind = $mainMod SHIFT, 4, movetoworkspace, 4
bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Example special workspace (scratchpad)
bind = $mainMod, S, togglespecialworkspace, magic
bind = CTRL $mainMod, S, movetoworkspace, special:magic
# Scroll through existing workspaces with mainMod + scroll
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1
# Move/resize windows with mainMod + LMB/RMB and dragging
bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow
# Laptop multimedia keys for volume and LCD brightness
bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
bindel = ,XF86MonBrightnessUp, exec, brightnessctl -e4 -n2 set 5%+
bindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%-
# Requires playerctl
bindl = , XF86AudioNext, exec, playerctl next
bindl = , XF86AudioPause, exec, playerctl play-pause
bindl = , XF86AudioPlay, exec, playerctl play-pause
bindl = , XF86AudioPrev, exec, playerctl previous
##############################
### WINDOWS AND WORKSPACES ###
##############################
windowrule {
# Ignore maximize requests from all apps. You'll probably like this.
name = suppress-maximize-events
match:class = .*
suppress_event = maximize
}
windowrule {
# Fix some dragging issues with XWayland
name = fix-xwayland-drags
match:class = ^$
match:title = ^$
match:xwayland = true
match:float = true
match:fullscreen = false
match:pin = false
no_focus = true
}
# Hyprland-run windowrule
windowrule {
name = move-hyprland-run
match:class = hyprland-run
move = 20 monitor_h-120
float = yes
}

98
config/hypr/hyprlock.conf Normal file
View File

@@ -0,0 +1,98 @@
# Generated by ~/.config/hypr/Scripts/theme-menu.sh
auth {
pam:enabled = true
pam:module = hyprlock
fingerprint:enabled = true
fingerprint:ready_message = Finger auflegen zum Entsperren
fingerprint:present_message = Fingerabdruck wird gelesen...
fingerprint:retry_delay = 250
}
general {
disable_loading_bar = true
hide_cursor = true
grace = 0
ignore_empty_input = true
}
background {
monitor =
path = /home/pascal/Bilder/Wallpaper/forest.jpg
blur_passes = 3
blur_size = 8
noise = 0.011
contrast = 1.05
brightness = 0.72
vibrancy = 0.18
vibrancy_darkness = 0.25
}
label {
monitor =
text = 󰌪 Forest Neon
color = rgba(0,204,136, 0.90)
font_size = 22
font_family = JetBrainsMono Nerd Font
position = 0, 260
halign = center
valign = center
}
label {
monitor =
text = cmd[update:1000] date +"%H:%M"
color = rgba(0,255,156, 0.96)
font_size = 92
font_family = JetBrainsMono Nerd Font
position = 0, 165
halign = center
valign = center
}
label {
monitor =
text = cmd[update:60000] date +"%A, %d. %B"
color = rgba(205,214,244, 0.82)
font_size = 22
font_family = JetBrainsMono Nerd Font
position = 0, 92
halign = center
valign = center
}
input-field {
monitor =
size = 390, 62
outline_thickness = 2
dots_size = 0.24
dots_spacing = 0.28
dots_center = true
fade_on_empty = false
rounding = 16
outer_color = rgba(0,255,156, 0.72)
inner_color = rgba(20,20,30, 0.76)
font_color = rgba(205,214,244, 0.92)
placeholder_text = <i>Passwort oder Fingerprint</i>
check_color = rgba(0,204,136, 0.88)
fail_color = rgba(243,139,168, 0.95)
capslock_color = rgba(249,226,175, 0.95)
position = 0, -20
halign = center
valign = center
}
label {
monitor =
text = cmd[update:1000] echo " pascal@PDEV-Yoga"
color = rgba(204,204,204, 0.70)
font_size = 16
font_family = JetBrainsMono Nerd Font
position = 0, -92
halign = center
valign = center
}

View File

@@ -0,0 +1,5 @@
wallpaper {
monitor =
path = /home/pascal/Bilder/Wallpaper/forest.jpg
fit_mode = cover
}

View File

@@ -0,0 +1,279 @@
import QtQuick 2.0
import SddmComponents 2.0
Rectangle {
id: root
width: 1920
height: 1080
color: config.backgroundColor || "#18141f"
property int sessionIndex: session.index
property color accent: config.accent || "#f38ba8"
property color accent2: config.accent2 || "#cba6f7"
property color backgroundColor: config.backgroundColor || "#18141f"
property color panelColor: config.panelColor || "#313244"
property color foreground: config.foreground || "#f5e0dc"
property color muted: config.muted || "#cdd6f4"
property color selectedText: config.selectedText || "#11111b"
property string themeName: config.themeName || "Rose Night"
TextConstants { id: textConstants }
Connections {
target: sddm
function onLoginSucceeded() {
message.text = textConstants.loginSucceeded
message.color = accent2
}
function onLoginFailed() {
password.text = ""
message.text = textConstants.loginFailed
message.color = "#f38ba8"
}
function onInformationMessage(text) {
message.text = text
message.color = "#f38ba8"
}
}
Background {
anchors.fill: parent
source: config.background || ""
fillMode: Image.PreserveAspectCrop
onStatusChanged: {
if (status === Image.Error) {
source = ""
}
}
}
Rectangle {
anchors.fill: parent
color: "#000000"
opacity: 0.42
}
Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, 0.10) }
GradientStop { position: 0.58; color: Qt.rgba(0, 0, 0, 0.38) }
GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.62) }
}
}
Timer {
interval: 1000
running: true
repeat: true
triggeredOnStart: true
onTriggered: {
clock.text = Qt.formatDateTime(new Date(), "HH:mm")
date.text = Qt.formatDateTime(new Date(), "dddd, dd. MMMM yyyy")
}
}
Column {
anchors.left: parent.left
anchors.leftMargin: Math.max(42, parent.width * 0.055)
anchors.top: parent.top
anchors.topMargin: Math.max(40, parent.height * 0.055)
spacing: 2
Text {
id: clock
color: foreground
font.pixelSize: Math.max(64, root.height * 0.105)
font.weight: Font.Light
}
Text {
id: date
color: muted
opacity: 0.88
font.pixelSize: Math.max(16, root.height * 0.023)
}
}
Rectangle {
id: panel
width: Math.min(430, root.width - 48)
height: 430
anchors.right: parent.right
anchors.rightMargin: Math.max(28, parent.width * 0.075)
anchors.verticalCenter: parent.verticalCenter
radius: 18
color: panelColor
opacity: 0.90
border.color: accent
border.width: 1
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: 4
radius: 2
gradient: Gradient {
GradientStop { position: 0.0; color: accent }
GradientStop { position: 1.0; color: accent2 }
}
}
Column {
anchors.fill: parent
anchors.margins: 34
spacing: 14
Text {
text: sddm.hostName
color: foreground
font.pixelSize: 26
font.bold: true
elide: Text.ElideRight
width: parent.width
}
Text {
text: themeName
color: accent
font.pixelSize: 14
width: parent.width
opacity: 0.92
}
Item { width: 1; height: 10 }
Text {
text: "Benutzer"
color: muted
font.pixelSize: 12
opacity: 0.78
}
TextBox {
id: username
width: parent.width
height: 42
text: userModel.lastUser
color: backgroundColor
borderColor: Qt.rgba(accent.r, accent.g, accent.b, 0.40)
focusColor: accent
hoverColor: accent2
textColor: foreground
radius: 10
font.pixelSize: 16
KeyNavigation.tab: password
}
Text {
text: "Passwort"
color: muted
font.pixelSize: 12
opacity: 0.78
}
PasswordBox {
id: password
width: parent.width
height: 42
color: backgroundColor
borderColor: Qt.rgba(accent.r, accent.g, accent.b, 0.40)
focusColor: accent
hoverColor: accent2
textColor: foreground
radius: 10
font.pixelSize: 16
KeyNavigation.backtab: username
KeyNavigation.tab: loginButton
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
sddm.login(username.text, password.text, sessionIndex)
event.accepted = true
}
}
}
ComboBox {
id: session
width: parent.width
height: 36
model: sessionModel
index: sessionModel.lastIndex
color: backgroundColor
borderColor: Qt.rgba(accent.r, accent.g, accent.b, 0.40)
focusColor: accent
hoverColor: accent2
menuColor: panelColor
textColor: foreground
arrowColor: accent
arrowIcon: "angle-down.png"
KeyNavigation.backtab: password
KeyNavigation.tab: loginButton
}
Button {
id: loginButton
width: parent.width
height: 44
text: "Anmelden"
color: accent
activeColor: accent2
pressedColor: accent2
disabledColor: muted
borderColor: accent2
textColor: selectedText
font.pixelSize: 15
onClicked: sddm.login(username.text, password.text, sessionIndex)
KeyNavigation.backtab: session
KeyNavigation.tab: shutdownButton
}
Text {
id: message
width: parent.width
color: muted
text: ""
font.pixelSize: 13
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
}
Row {
anchors.right: parent.right
anchors.rightMargin: Math.max(28, parent.width * 0.075)
anchors.bottom: parent.bottom
anchors.bottomMargin: Math.max(26, parent.height * 0.045)
spacing: 10
Button {
id: shutdownButton
text: "Ausschalten"
color: Qt.rgba(0, 0, 0, 0.42)
activeColor: accent
pressedColor: accent2
borderColor: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.35)
textColor: foreground
onClicked: sddm.powerOff()
KeyNavigation.tab: rebootButton
}
Button {
id: rebootButton
text: "Neustart"
color: Qt.rgba(0, 0, 0, 0.42)
activeColor: accent
pressedColor: accent2
borderColor: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.35)
textColor: foreground
onClicked: sddm.reboot()
KeyNavigation.backtab: shutdownButton
}
}
}

View File

@@ -0,0 +1,13 @@
[SddmGreeterTheme]
Name=Pascal Hypr
Description=Theme-aware SDDM login screen for Pascal's Hyprland setup
Author=Pascal + Codex
Copyright=2026
License=MIT
Type=sddm-theme
Version=1.0
MainScript=Main.qml
ConfigFile=theme.conf
Theme-Id=pascal-hypr
Theme-API=2.0

View File

@@ -0,0 +1,11 @@
[General]
background=/var/lib/pascal-sddm-theme/wallpaper.jpg
themeName=Rose Night
accent=#f38ba8
accent2=#cba6f7
backgroundColor=#18141f
panelColor=#313244
foreground=#f5e0dc
muted=#cdd6f4
selectedText=#11111b

View File

@@ -0,0 +1,6 @@
[Autologin]
Session=hyprland
[Theme]
Current=pascal-hypr