diff --git a/.config/alacritty/alacritty.toml b/.config/alacritty/alacritty.toml new file mode 100644 index 0000000..7a71ee9 --- /dev/null +++ b/.config/alacritty/alacritty.toml @@ -0,0 +1,96 @@ +[env] +TERM = "xterm-256color" + +[window] +padding = { x = 10, y = 10 } +opacity = 1.0 + +[font] +size = 11.0 + +[font.normal] +family = "SauceCodePro Nerd Font" +style = "Regular" + +[font.bold] +family = "SauceCodePro Nerd Font" +style = "Bold" + +[font.italic] +family = "SauceCodePro Nerd Font" +style = "Italic" + +[cursor] +style = { shape = "Beam", blinking = "Off" } +unfocused_hollow = true + +[selection] +save_to_clipboard = true + +[colors.primary] +background = "#2E3440" +foreground = "#D8DEE9" +dim_foreground = "#A5ABB6" +bright_foreground = "#ECEFF4" + +[colors.cursor] +text = "#2E3440" +cursor = "#D8DEE9" + +[colors.vi_mode_cursor] +text = "#2E3440" +cursor = "#88C0D0" + +[colors.search.matches] +foreground = "#2E3440" +background = "#EBCB8B" + +[colors.search.focused_match] +foreground = "#2E3440" +background = "#A3BE8C" + +[colors.footer_bar] +foreground = "#D8DEE9" +background = "#3B4252" + +[colors.hints.start] +foreground = "#2E3440" +background = "#EBCB8B" + +[colors.hints.end] +foreground = "#2E3440" +background = "#4C566A" + +[colors.line_indicator] +foreground = "None" +background = "None" + +[colors.normal] +black = "#3B4252" +red = "#BF616A" +green = "#A3BE8C" +yellow = "#EBCB8B" +blue = "#81A1C1" +magenta = "#B48EAD" +cyan = "#88C0D0" +white = "#E5E9F0" + +[colors.bright] +black = "#4C566A" +red = "#BF616A" +green = "#A3BE8C" +yellow = "#EBCB8B" +blue = "#81A1C1" +magenta = "#B48EAD" +cyan = "#8FBCBB" +white = "#ECEFF4" + +[colors.dim] +black = "#373E4D" +red = "#94545D" +green = "#809575" +yellow = "#B29E75" +blue = "#68809A" +magenta = "#8C738C" +cyan = "#6D96A5" +white = "#AEB3BB" diff --git a/.config/hypr/monitors.json b/.config/hypr/monitors.json index 06c498a..e906caa 100644 --- a/.config/hypr/monitors.json +++ b/.config/hypr/monitors.json @@ -3,15 +3,6 @@ { "name": "Defaultmon-not-found", "monitors": { - "desc:Dell Inc. DELL P2422HE HFKK5L3": { - "resolution": "1920x1080", - "refresh_rate": 60.0, - "scale": 1.0, - "rotation": 0, - "position_x": 0, - "position_y": 0, - "is_primary": false - }, "desc:Microstep MEG 342C OLED": { "resolution": "3440x1440", "refresh_rate": 175.0, @@ -29,13 +20,66 @@ "position_x": 5360, "position_y": 0, "is_primary": false + }, + "desc:Dell Inc. DELL P2422HE HFKK5L3": { + "resolution": "1920x1080", + "refresh_rate": 60.0, + "scale": 1.0, + "rotation": 0, + "position_x": 0, + "position_y": 0, + "is_primary": false } } }, { "name": "l -ba /home/zphinx/.", "monitors": {} + }, + { + "name": "d /home/zphinx/code/", + "monitors": {} + }, + { + "name": " echo \"$p :: syml", + "monitors": {} + }, + { + "name": " echo \"$p :: file", + "monitors": {} + }, + { + "name": "g\" 2>/dev/null || tr", + "monitors": {} + }, + { + "name": "x/.config/waybar/scr", + "monitors": {} + }, + { + "name": "x/code/mine status -", + "monitors": {} + }, + { + "name": "ohup waybar -c /home", + "monitors": {} + }, + { + "name": "eline -- dotfiles/.c", + "monitors": {} + }, + { + "name": "s-serif' && fc-match", + "monitors": {} + }, + { + "name": "eeded ttf-fira-code ", + "monitors": {} + }, + { + "name": " /home/zphinx/.confi", + "monitors": {} } ], - "active_workspace": 1 + "active_workspace": 12 } \ No newline at end of file diff --git a/.config/waybar/config.jsonc b/.config/waybar/config.jsonc index 401b7c0..dc39ef5 100644 --- a/.config/waybar/config.jsonc +++ b/.config/waybar/config.jsonc @@ -10,6 +10,8 @@ "clock" ], "modules-right": [ + "custom/spotify", + "custom/weather", "custom/screenshot", "idle_inhibitor", "pulseaudio", @@ -23,28 +25,50 @@ "format": "{id}" }, "custom/screenshot": { - "exec": "printf '{\"text\": \"󰄀\", \"tooltip\": \"Area screenshot: edit in Swappy, copy to clipboard, save to NAS pictures if mounted\"}'", + "exec": "printf '{\"text\": \"Shot 󰄀\", \"tooltip\": \"Area screenshot: edit in Swappy, then choose save path and filename; Esc on filename uses screenshot\"}'", "return-type": "json", + "escape": false, "interval": "once", "on-click": "$HOME/.config/waybar/scripts/capture-share-shot.sh" }, "idle_inhibitor": { + "escape": false, "format": "{icon}", "format-icons": { - "activated": "", - "deactivated": "" + "activated": "Awake ", + "deactivated": "Idle " } }, "tray": { "spacing": 10 }, "clock": { - "format": " {:%H:%M}", + "escape": false, + "format": "{:%H:%M} ", "tooltip-format": "{:%Y %B}\n{calendar}", - "format-alt": " {:%Y-%m-%d}" + "format-alt": "{:%Y-%m-%d} " + }, + "custom/spotify": { + "exec": "bash $HOME/.config/waybar/scripts/spotify-waybar.sh", + "return-type": "json", + "interval": 2, + "escape": false, + "on-click": "bash $HOME/.config/waybar/scripts/spotify-waybar.sh --toggle", + "on-click-right": "bash $HOME/.config/waybar/scripts/spotify-waybar.sh --stop", + "on-scroll-up": "bash $HOME/.config/waybar/scripts/spotify-waybar.sh --next", + "on-scroll-down": "bash $HOME/.config/waybar/scripts/spotify-waybar.sh --prev", + "tooltip": false + }, + "custom/weather": { + "exec": "bash $HOME/.config/waybar/scripts/weather-norrkoping.sh", + "return-type": "json", + "interval": 600, + "escape": false, + "tooltip": true }, "cpu": { - "format": " {usage}%", + "escape": false, + "format": "{usage}% ", "tooltip": false, "on-click": "alacritty -e btop" }, @@ -52,6 +76,7 @@ "custom/mount-data": { "exec": "$HOME/.config/waybar/scripts/check-mount.sh /mnt/data data", "return-type": "json", + "escape": false, "interval": 10, "tooltip": true, "on-click": "thunar /mnt/data" @@ -59,34 +84,37 @@ "custom/mount-usb1": { "exec": "$HOME/.config/waybar/scripts/check-mount.sh /mnt/usb1 usb1", "return-type": "json", + "escape": false, "interval": 10, "tooltip": true, "on-click": "thunar /mnt/usb1" }, "network": { - "format-wifi": " {essid} ({signalStrength}%)", - "format-ethernet": "󰈀 {ipaddr}/{cidr}", - "tooltip-format": "󰈀 {ifname} via {gwaddr}", - "format-linked": "󰈀 {ifname} (No IP)", - "format-disconnected": "󰖪 Disconnected", - "format-alt": "󰈀 {ifname}: {ipaddr}/{cidr}", + "escape": false, + "format-wifi": "{essid} ({signalStrength}%) ", + "format-ethernet": "{ipaddr}/{cidr} 󰈀", + "tooltip-format": "{ifname} via {gwaddr} 󰈀", + "format-linked": "{ifname} (No IP) 󰈀", + "format-disconnected": "Disconnected 󰖪", + "format-alt": "{ifname}: {ipaddr}/{cidr} 󰈀", "on-click-right": "nm-connection-editor" }, "pulseaudio": { - "format": "{icon} {volume}% {format_source}", - "format-bluetooth": "{icon} {volume}%  {format_source}", - "format-bluetooth-muted": " {icon}  {format_source}", - "format-muted": " {format_source}", - "format-source": " {volume}%", - "format-source-muted": "", + "escape": false, + "format": "{volume}% {format_source} {icon}", + "format-bluetooth": "{volume}% {format_source} {icon}", + "format-bluetooth-muted": "{format_source} {icon} ", + "format-muted": "{format_source} ", + "format-source": "{volume}% ", + "format-source-muted": "", "format-icons": { - "headphone": "", - "hands-free": "", - "headset": "", - "phone": "", - "portable": "", - "car": "", - "default": ["", "", ""] + "headphone": "", + "hands-free": "", + "headset": "", + "phone": "", + "portable": "", + "car": "", + "default": ["", "", ""] }, "on-click": "pavucontrol" } diff --git a/.config/waybar/scripts/capture-share-shot.sh b/.config/waybar/scripts/capture-share-shot.sh index d7b189f..1e3e882 100755 --- a/.config/waybar/scripts/capture-share-shot.sh +++ b/.config/waybar/scripts/capture-share-shot.sh @@ -11,7 +11,14 @@ cleanup() { } trap cleanup EXIT -choose_subfolder() { +trim_whitespace() { + local value="$1" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + printf '%s\n' "$value" +} + +choose_destination_dir() { if [[ ! -d "$base_dir" ]] || ! mountpoint -q /mnt/data; then return 1 fi @@ -21,7 +28,7 @@ choose_subfolder() { find "$base_dir" -mindepth 1 -type d -printf '%P\n' | sort } > "$selection_file" - if selected=$(wofi --dmenu --prompt 'Save screenshot folder' < "$selection_file"); then + if selected=$(wofi --dmenu --prompt 'Save screenshot path' < "$selection_file"); then if [[ -z "$selected" || "$selected" == "/" ]]; then printf '%s\n' "$base_dir" else @@ -33,6 +40,47 @@ choose_subfolder() { printf '%s\n' "$base_dir" } +choose_filename() { + local default_name="screenshot" + local selected="" + + if selected=$(printf '%s\n' "$default_name" | wofi --dmenu --prompt 'Save screenshot filename'); then + selected=$(trim_whitespace "$selected") + else + selected="$default_name" + fi + + if [[ -z "$selected" ]]; then + selected="$default_name" + fi + + selected="${selected%.png}" + + if [[ -z "$selected" ]]; then + selected="$default_name" + fi + + printf '%s\n' "$selected" +} + +build_unique_path() { + local destination_dir="$1" + local file_stem="$2" + local candidate="$destination_dir/$file_stem.png" + local counter=1 + + if [[ ! -e "$candidate" ]]; then + printf '%s\n' "$candidate" + return + fi + + while [[ -e "$destination_dir/$file_stem-$counter.png" ]]; do + counter=$((counter + 1)) + done + + printf '%s\n' "$destination_dir/$file_stem-$counter.png" +} + if ! region="$(slurp)"; then exit 0 fi @@ -43,11 +91,13 @@ fi wl-copy --type image/png < "$tmp_file" -if destination_dir="$(choose_subfolder)"; then +if destination_dir="$(choose_destination_dir)"; then mkdir -p "$destination_dir" - file_name="screenshot-$(date +%Y%m%d-%H%M%S).png" - cp "$tmp_file" "$destination_dir/$file_name" - notify-send 'Screenshot saved' "$destination_dir/$file_name\nCopied to clipboard" + file_stem="$(choose_filename)" + destination_path="$(build_unique_path "$destination_dir" "$file_stem")" + cp "$tmp_file" "$destination_path" + notify-send 'Screenshot saved' "$destination_path +Copied to clipboard" else notify-send 'Screenshot copied' 'NAS picture share is not available; image copied to clipboard only' fi diff --git a/.config/waybar/scripts/check-mount.sh b/.config/waybar/scripts/check-mount.sh index 4996d6b..c9edf31 100755 --- a/.config/waybar/scripts/check-mount.sh +++ b/.config/waybar/scripts/check-mount.sh @@ -7,10 +7,37 @@ LABEL="$2" ICON_MOUNT="󰋊" ICON_DOWN="" +json_escape() { + local s="$1" + s=${s//\\/\\\\} + s=${s//\"/\\\"} + s=${s//$'\n'/\\n} + printf '%s' "$s" +} + +markup_escape() { + local s="$1" + s=${s//&/&} + s=${s///>} + printf '%s' "$s" +} + +icon_markup() { + local icon="$1" + local color="$2" + printf '%s' "$color" "$icon" +} + if mountpoint -q "$MOUNT"; then read -r used avail pcent <<< "$(df -h --output=used,avail,pcent "$MOUNT" | tail -1)" - printf '{"text": "%s %s %s/%s (%s)", "tooltip": "%s mounted — used: %s, free: %s (%s)", "class": "mounted"}\n' \ - "$ICON_MOUNT" "$LABEL" "$used" "$avail" "$pcent" "$MOUNT" "$used" "$avail" "$pcent" + text="$(markup_escape "$LABEL $used/$avail ($pcent)") $(icon_markup "$ICON_MOUNT" "#A3BE8C")" + tooltip="$(markup_escape "$MOUNT mounted - used: $used, free: $avail ($pcent)")" + printf '{"text":"%s","tooltip":"%s","class":"mounted"}\n' \ + "$(json_escape "$text")" "$(json_escape "$tooltip")" else - printf '{"text": "%s %s N/A", "tooltip": "%s is NOT mounted", "class": "unmounted"}\n' "$ICON_DOWN" "$LABEL" "$MOUNT" + text="$(markup_escape "$LABEL N/A") $(icon_markup "$ICON_DOWN" "#BF616A")" + tooltip="$(markup_escape "$MOUNT is NOT mounted")" + printf '{"text":"%s","tooltip":"%s","class":"unmounted"}\n' \ + "$(json_escape "$text")" "$(json_escape "$tooltip")" fi diff --git a/.config/waybar/scripts/spotify-waybar.sh b/.config/waybar/scripts/spotify-waybar.sh new file mode 100644 index 0000000..d5a4b16 --- /dev/null +++ b/.config/waybar/scripts/spotify-waybar.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash +set -u + +CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/waybar-spotify" +ART_URL_FILE="$CACHE_DIR/art_url" +ART_FILE="$CACHE_DIR/cover.jpg" +PLAYER="" + +mkdir -p "$CACHE_DIR" + +json_escape() { + local s="$1" + s=${s//\\/\\\\} + s=${s//\"/\\\"} + s=${s//$'\n'/\\n} + printf '%s' "$s" +} + +markup_escape() { + local s="$1" + s=${s//&/&} + s=${s///>} + printf '%s' "$s" +} + +icon_markup() { + local icon="$1" + local color="$2" + printf '%s' "$color" "$icon" +} + +truncate_text() { + local text="$1" + local max_len="$2" + if (( ${#text} <= max_len )); then + printf '%s' "$text" + return + fi + printf '%s...' "${text:0:max_len-3}" +} + +pick_player() { + local candidates + local p + local c + + mapfile -t candidates < <(playerctl -l 2>/dev/null) + + if (( ${#candidates[@]} == 0 )); then + return 1 + fi + + for c in spotify spotifyd ncspot nspot; do + for p in "${candidates[@]}"; do + if [[ "$p" == "$c" || "$p" == "$c."* ]]; then + PLAYER="$p" + return 0 + fi + done + done + + PLAYER="${candidates[0]}" + return 0 +} + +download_cover_if_needed() { + local art_url="$1" + + if [[ -z "$art_url" ]]; then + return + fi + + local old_url="" + if [[ -f "$ART_URL_FILE" ]]; then + old_url=$(cat "$ART_URL_FILE" 2>/dev/null) + fi + + if [[ "$art_url" == "$old_url" ]]; then + return + fi + + printf '%s' "$art_url" > "$ART_URL_FILE" + + if [[ "$art_url" == file://* ]]; then + local local_path="${art_url#file://}" + if [[ -f "$local_path" ]]; then + cp -f "$local_path" "$ART_FILE" 2>/dev/null || true + fi + return + fi + + if command -v curl >/dev/null 2>&1; then + curl -LfsS --max-time 4 "$art_url" -o "$ART_FILE" 2>/dev/null || true + fi +} + +open_cover() { + if [[ -f "$ART_FILE" ]]; then + xdg-open "$ART_FILE" >/dev/null 2>&1 & + fi +} + +control_player() { + local action="$1" + case "$action" in + --toggle) + playerctl -p "$PLAYER" play-pause >/dev/null 2>&1 || true + ;; + --next) + playerctl -p "$PLAYER" next >/dev/null 2>&1 || true + ;; + --prev) + playerctl -p "$PLAYER" previous >/dev/null 2>&1 || true + ;; + --stop) + playerctl -p "$PLAYER" stop >/dev/null 2>&1 || true + ;; + --open-art) + open_cover + ;; + esac +} + +if [[ $# -gt 0 ]]; then + control_player "$1" + exit 0 +fi + +if ! command -v playerctl >/dev/null 2>&1; then + printf '{"text":"%s","tooltip":"playerctl missing: install playerctl for media controls","class":"idle","alt":"idle"}\n' "$(json_escape "$(icon_markup "" "#A3BE8C")")" + exit 0 +fi + +if ! pick_player; then + printf '{"text":"%s","tooltip":"No active player","class":"idle","alt":"idle"}\n' "$(json_escape "$(icon_markup "" "#A3BE8C")")" + exit 0 +fi + +status=$(playerctl -p "$PLAYER" status 2>/dev/null || true) +if [[ -z "$status" ]]; then + printf '{"text":"%s","tooltip":"No active player status","class":"idle","alt":"idle"}\n' "$(json_escape "$(icon_markup "" "#A3BE8C")")" + exit 0 +fi + +title=$(playerctl -p "$PLAYER" metadata xesam:title 2>/dev/null || true) +artist=$(playerctl -p "$PLAYER" metadata xesam:artist 2>/dev/null || true) +album=$(playerctl -p "$PLAYER" metadata xesam:album 2>/dev/null || true) +art_url=$(playerctl -p "$PLAYER" metadata mpris:artUrl 2>/dev/null || true) + +state_class=$(printf '%s' "$status" | tr '[:upper:]' '[:lower:]') + +if [[ "$state_class" == "stopped" ]]; then + text_markup="Stopped $(icon_markup "" "#D08770") $(icon_markup "" "#A3BE8C")" + printf '{"text":"%s","class":"stopped","alt":"stopped"}\n' "$(json_escape "$text_markup")" + exit 0 +fi + +if [[ -z "$artist" && -z "$title" ]]; then + printf '{"text":"%s","class":"idle","alt":"idle"}\n' "$(json_escape "$(icon_markup "" "#A3BE8C")")" + exit 0 +fi + +if [[ -z "$title" ]]; then + title="Unknown title" +fi +if [[ -z "$artist" ]]; then + artist="Unknown artist" +fi +if [[ -z "$album" ]]; then + album="Unknown album" +fi + +download_cover_if_needed "$art_url" + +display_track="$(truncate_text "$artist - $title" 52)" +state_icon="" +if [[ "$state_class" == "paused" ]]; then + state_icon="" +fi + +app_icon="" +if [[ "$PLAYER" == spotify* || "$PLAYER" == spotifyd* || "$PLAYER" == *ncspot* || "$PLAYER" == *nspot* ]]; then + app_icon="" +fi + +track_color="#ECEFF4" +state_color="#88C0D0" +app_color="#A3BE8C" + +if [[ "$state_class" == "paused" ]]; then + track_color="#E5E9F0" + state_color="#EBCB8B" +fi + +cover_line="Cover art: unavailable" +if [[ -f "$ART_FILE" ]]; then + cover_line="Cover art: cached (right-click opens image)" +fi + +tooltip=$(cat <$(markup_escape "$display_track") $(icon_markup "$state_icon" "$state_color") $(icon_markup "$app_icon" "$app_color")" + +printf '{"text":"%s","tooltip":"%s","class":"%s","alt":"%s"}\n' \ + "$(json_escape "$text_markup")" \ + "$(json_escape "$tooltip")" \ + "$(json_escape "$state_class")" \ + "$(json_escape "$state_class")" diff --git a/.config/waybar/scripts/weather-norrkoping.sh b/.config/waybar/scripts/weather-norrkoping.sh new file mode 100755 index 0000000..b5f0955 --- /dev/null +++ b/.config/waybar/scripts/weather-norrkoping.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +set -u + +LOCATION="Norrkoping" +URL="https://wttr.in/${LOCATION}?format=%t|%C" + +json_escape() { + local s="$1" + s=${s//\\/\\\\} + s=${s//\"/\\\"} + s=${s//$'\n'/\\n} + printf '%s' "$s" +} + +markup_escape() { + local s="$1" + s=${s//&/&} + s=${s///>} + printf '%s' "$s" +} + +icon_markup() { + local icon="$1" + local color="$2" + printf '%s' "$color" "$icon" +} + +icon_for_condition() { + local c + c=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]') + + case "$c" in + *thunder*|*storm*) + printf '󰖓' + ;; + *snow*|*sleet*|*blizzard*|*ice*) + printf '󰼶' + ;; + *rain*|*drizzle*|*shower*) + printf '󰖗' + ;; + *fog*|*mist*|*haze*|*smoke*) + printf '󰖑' + ;; + *partly*) + printf '󰖕' + ;; + *clear*|*sunny*) + printf '󰖙' + ;; + *cloud*|*overcast*) + printf '󰖐' + ;; + *) + printf '󰖐' + ;; + esac +} + +if ! command -v curl >/dev/null 2>&1; then + text="--°C Unavailable $(icon_markup "󰖐" "#D08770")" + printf '{"text":"%s","tooltip":"curl is required for weather","class":"weather-unavailable"}\n' "$(json_escape "$text")" + exit 0 +fi + +raw=$(curl -fsS --max-time 6 "$URL" 2>/dev/null || true) +if [[ -z "$raw" || "$raw" != *"|"* ]]; then + text="--°C Unavailable $(icon_markup "󰖐" "#D08770")" + printf '{"text":"%s","tooltip":"Weather unavailable for Norrkoping","class":"weather-unavailable"}\n' "$(json_escape "$text")" + exit 0 +fi + +temp=${raw%%|*} +condition=${raw#*|} +icon=$(icon_for_condition "$condition") +updated=$(date '+%H:%M') +text="$(markup_escape "$temp $condition") $(icon_markup "$icon" "#88C0D0")" +tooltip=$(cat </dev/null 2>&1; then + echo "This installer currently supports Arch Linux (pacman) only." + exit 1 +fi + +PACKAGES=( + # Core desktop/session + hyprland + hyprpaper + waybar + xdg-desktop-portal + xdg-desktop-portal-hyprland + + # Terminal + launcher + file manager + alacritty + wofi + thunar + + # Audio/network/system controls used by config + pipewire + wireplumber + pavucontrol + playerctl + brightnessctl + networkmanager + network-manager-applet + + # Wayland screenshot stack + notifications + grim + slurp + swappy + wl-clipboard + libnotify + + # Utilities used by scripts/config + curl + btop + tmux + fzf + xclip + kdeconnect +) + +REQUIRED_COMMANDS=( + hyprland + hyprpaper + waybar + alacritty + wofi + thunar + wpctl + playerctl + brightnessctl + grim + slurp + swappy + wl-copy + notify-send + curl + btop +) + +echo "Installing packages for dotfiles setup..." +sudo pacman -S --needed --noconfirm "${PACKAGES[@]}" + +missing=() +for cmd in "${REQUIRED_COMMANDS[@]}"; do + if ! command -v "$cmd" >/dev/null 2>&1; then + missing+=("$cmd") + fi +done + +if (( ${#missing[@]} > 0 )); then + echo + echo "Install completed, but these commands are still missing:" + printf ' - %s\n' "${missing[@]}" + echo "Check package names/repos on your system and install them manually." + exit 2 +fi + +echo +echo "Install complete." +echo "Next steps:" +echo "1) Symlink/copy dotfiles into ~/.config and ~/.tmux.conf" +echo "2) Enable/start NetworkManager if needed: sudo systemctl enable --now NetworkManager" +echo "3) Start Hyprland" +#!/usr/bin/env bash +# install.sh — bootstrap dotfiles on a fresh Arch/CachyOS install +# Usage: ./install.sh [--dry-run] +set -euo pipefail + +DOTFILES="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DRY_RUN=false +[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true + +# ------------------------------------------------------- +# Helpers +# ------------------------------------------------------- + +info() { echo -e "\e[34m[INFO]\e[0m $*"; } +ok() { echo -e "\e[32m[ OK ]\e[0m $*"; } +warn() { echo -e "\e[33m[WARN]\e[0m $*"; } +error() { echo -e "\e[31m[ERR ]\e[0m $*"; } +dry() { echo -e "\e[90m[DRY ]\e[0m $*"; } + +run() { + if $DRY_RUN; then + dry "$*" + else + "$@" + fi +} + +# ------------------------------------------------------- +# 1. Check: Wayland / Hyprland environment +# ------------------------------------------------------- + +info "Checking display server..." + +if [[ -z "${WAYLAND_DISPLAY:-}" && -z "${XDG_SESSION_TYPE:-}" ]]; then + warn "WAYLAND_DISPLAY not set — are you running a Wayland session?" +elif [[ "${XDG_SESSION_TYPE:-}" == "x11" ]]; then- + warn "Session type is X11. Hyprland and Waybar require Wayland." +else + ok "Wayland session detected (${XDG_SESSION_TYPE:-wayland})" +fi + +# ------------------------------------------------------- +# 2. Check: Required programs +# ------------------------------------------------------- + +info "Checking required programs..." + +REQUIRED=( + hyprland + waybar + hyprpaper + alacritty + grim + slurp + swappy + wl-copy + wofi + thunar + btop + nm-connection-editor + kdeconnect-indicator + expac + pavucontrol + notify-send +) + +MISSING=() +for cmd in "${REQUIRED[@]}"; do + if command -v "$cmd" &>/dev/null; then + ok " $cmd" + else + warn " $cmd — NOT FOUND" + MISSING+=("$cmd") + fi +done + +if [[ ${#MISSING[@]} -gt 0 ]]; then + warn "Missing programs: ${MISSING[*]}" + read -rp "Install missing packages with yay now? [y/N] " ans + if [[ "${ans,,}" == "y" ]]; then + run yay -S --needed "${MISSING[@]}" + else + warn "Skipping package install — some features may not work." + fi +else + ok "All required programs found." +fi + +# ------------------------------------------------------- +# 3. Check: Required fonts +# ------------------------------------------------------- + +info "Checking fonts..." + +FONTS=( + ttf-sourcecodepro-nerd + ttf-nerd-fonts-symbols + ttf-nerd-fonts-symbols-mono + otf-font-awesome +) + +MISSING_FONTS=() +for pkg in "${FONTS[@]}"; do + if pacman -Q "$pkg" &>/dev/null; then + ok " $pkg" + else + warn " $pkg — NOT INSTALLED" + MISSING_FONTS+=("$pkg") + fi +done + +if [[ ${#MISSING_FONTS[@]} -gt 0 ]]; then + read -rp "Install missing fonts with yay now? [y/N] " ans + if [[ "${ans,,}" == "y" ]]; then + run yay -S --needed "${MISSING_FONTS[@]}" + run fc-cache -fv + else + warn "Skipping font install — icons in Waybar may not render." + fi +else + ok "All required fonts installed." +fi + +# ------------------------------------------------------- +# 4. Symlinks +# ------------------------------------------------------- + +info "Creating symlinks..." + +symlink() { + local src="$1" + local dst="$2" + local parent + parent="$(dirname "$dst")" + + if [[ ! -e "$src" ]]; then + error "Source does not exist: $src — skipping" + return + fi + + run mkdir -p "$parent" + + if [[ -L "$dst" ]]; then + local current + current="$(readlink "$dst")" + if [[ "$current" == "$src" ]]; then + ok " $dst (already linked)" + return + else + warn " $dst points elsewhere ($current) — relinking" + run ln -sfn "$src" "$dst" + fi + elif [[ -e "$dst" ]]; then + local bak="${dst}.bak.$(date +%Y%m%d_%H%M%S)" + warn " $dst exists — backing up to $bak" + run mv "$dst" "$bak" + run ln -s "$src" "$dst" + else + run ln -s "$src" "$dst" + ok " $dst → $src" + fi +} + +symlink "$DOTFILES/.bashrc" "$HOME/.bashrc" +symlink "$DOTFILES/.config/hypr" "$HOME/.config/hypr" +symlink "$DOTFILES/.config/waybar" "$HOME/.config/waybar" +symlink "$DOTFILES/.config/alacritty" "$HOME/.config/alacritty" +symlink "$DOTFILES/.local/bin/waybar" "$HOME/.local/bin/waybar" + +# Ensure waybar scripts are executable +if [[ -d "$DOTFILES/.config/waybar/scripts" ]]; then + run chmod +x "$DOTFILES/.config/waybar/scripts/"*.sh + ok "Waybar scripts marked executable" +fi + +# ------------------------------------------------------- +# 5. Check: NAS mount points +# ------------------------------------------------------- + +info "Checking NAS mount points..." + +for mnt in /mnt/data /mnt/usb1; do + if mountpoint -q "$mnt" 2>/dev/null; then + ok " $mnt is mounted" + elif [[ -d "$mnt" ]]; then + warn " $mnt exists but is not mounted" + else + warn " $mnt does not exist — Waybar mount checks will show unmounted" + run sudo mkdir -p "$mnt" + fi +done + +# ------------------------------------------------------- +# Done +# ------------------------------------------------------- + +echo "" +ok "Dotfiles installed! Start a new shell or run: source ~/.bashrc" +if command -v hyprland &>/dev/null; then + ok "Log out and back in to start Hyprland with Waybar." +fi