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//<}
+ 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//<}
+ 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//<}
+ 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