From feaeff3464dfd682fff3e1b645734854837c957a Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 9 Feb 2026 13:59:37 -0500 Subject: [PATCH] cleanup --- home/home.nix | 4 +- scripts/ctl | 110 ++++++++++++++++- scripts/doc | 37 +++++- scripts/hypr | 335 +++++++++++++++++++++++++++++++++++++++++++++++++- scripts/mux | 242 +++++++++++++++++++++++++++++++++++- scripts/pipes | 228 +++++++++++++++++++++++++++++++++- scripts/theme | 135 +++++++++++++++++++- scripts/wp | 149 +++++++++++++++++++++- scripts/x | 69 ++++++++++- 9 files changed, 1299 insertions(+), 10 deletions(-) mode change 120000 => 100755 scripts/ctl mode change 120000 => 100755 scripts/doc mode change 120000 => 100755 scripts/hypr mode change 120000 => 100755 scripts/mux mode change 120000 => 100755 scripts/pipes mode change 120000 => 100755 scripts/theme mode change 120000 => 100755 scripts/wp mode change 120000 => 100755 scripts/x diff --git a/home/home.nix b/home/home.nix index 4dd5674..16a3a6c 100644 --- a/home/home.nix +++ b/home/home.nix @@ -32,8 +32,8 @@ in news.display = "silent"; home.file.".local/bin/scripts" = { - source = ../scripts; - recursive = true; + source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/nix-config/scripts"; + recursive = false; executable = true; }; diff --git a/scripts/ctl b/scripts/ctl deleted file mode 120000 index 4181d44..0000000 --- a/scripts/ctl +++ /dev/null @@ -1 +0,0 @@ -/nix/store/i53klf9mw9p2k5nfawr6r2b0v3jyq1si-home-manager-files/.local/bin/scripts/ctl \ No newline at end of file diff --git a/scripts/ctl b/scripts/ctl new file mode 100755 index 0000000..2e6ba10 --- /dev/null +++ b/scripts/ctl @@ -0,0 +1,109 @@ +#!/bin/sh + +require() { + for cmd in "$@"; do + command -v "$cmd" >/dev/null 2>&1 || { + echo "ctl: missing dependency: $cmd" >&2 + exit 1 + } + done +} + +case "$1" in +screenshot) + dir="$HOME/img/ss" + mkdir -p "$dir" + file="$dir/$(openssl rand -hex 10)-$(date +'%Y-%m-%d_%H-%M-%S').png" + if [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then + require grim slurp wl-copy + grim -g "$(slurp)" "$file" && wl-copy < "$file" + else + require maim xclip + maim -s "$file" && xclip -selection clipboard -t image/png -i "$file" & + fi + ;; +ocr) + require tesseract + dir="$HOME/img/ss" + mkdir -p "$dir" + file="$dir/$(openssl rand -hex 10)-$(date +'%Y-%m-%d_%H-%M-%S').png" + if [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then + require grim slurp wl-copy + ( + region="$(slurp)" + [ -n "$region" ] || exit 0 + grim -g "$region" "$file" && + tesseract -l eng "$file" - 2>/dev/null | wl-copy + ) /dev/null 2>&1 & + else + require maim xclip + ( + maim -s "$file" && + tesseract -l eng "$file" - 2>/dev/null | xclip -selection clipboard -in + ) /dev/null 2>&1 & + fi + ;; +keyboard) + case "$2" in + toggle) + if [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then + if [ "$XDG_CURRENT_DESKTOP" = "Hyprland" ]; then + hyprctl switchxkblayout current next + elif [ "$XDG_CURRENT_DESKTOP" = "sway" ]; then + if swaymsg -t get_inputs | grep -qi "Colemak"; then + swaymsg input type:keyboard xkb_variant "''" + else + swaymsg input type:keyboard xkb_variant colemak + fi + fi + else + current="$(setxkbmap -query | awk '/variant/{print $2}')" + if [ "$current" = "colemak" ]; then + setxkbmap -layout us + else + setxkbmap -layout us -variant colemak + fi + fi + ;; + *) + echo "Usage: ctl keyboard {toggle}" >&2 + exit 1 + ;; + esac + ;; +audio) + require pactl + case "$2" in + out) + sinks="$(pactl list short sinks | awk '{print $1": "$2}')" + [ -z "$sinks" ] && exit 0 + count="$(printf "%s\n" "$sinks" | wc -l)" + if [ "$XDG_SESSION_TYPE" = x11 ]; then + choice="$(printf "%s\n" "$sinks" | dmenu -i -l "$count" -p "select sink:" | cut -d: -f1)" + else + choice="$(printf "%s\n" "$sinks" | rofi -dmenu -i -lines "$count" -p "select sink" | cut -d: -f1)" + fi + [ "$choice" ] && pactl set-default-sink "$choice" + ;; + in) + sources="$(pactl list short sources | awk '{print $1": "$2}')" + [ -z "$sources" ] && exit 0 + count="$(printf "%s\n" "$sources" | wc -l)" + if [ "$XDG_SESSION_TYPE" = x11 ]; then + choice="$(printf "%s\n" "$sources" | dmenu -i -l "$count" -p "select source:" | cut -d: -f1)" + else + choice="$(printf "%s\n" "$sources" | rofi -dmenu -i -lines "$count" -p "select source" | cut -d: -f1)" + fi + [ "$choice" ] && pactl set-default-source "$choice" + ;; + *) + echo "Usage: ctl audio {in|out}" >&2 + exit 1 + ;; + esac + ;; +*) + echo "Usage: ctl {screenshot|ocr|keyboard|audio}" >&2 + exit 1 + ;; +esac diff --git a/scripts/doc b/scripts/doc deleted file mode 120000 index 66bd887..0000000 --- a/scripts/doc +++ /dev/null @@ -1 +0,0 @@ -/nix/store/i53klf9mw9p2k5nfawr6r2b0v3jyq1si-home-manager-files/.local/bin/scripts/doc \ No newline at end of file diff --git a/scripts/doc b/scripts/doc new file mode 100755 index 0000000..2167604 --- /dev/null +++ b/scripts/doc @@ -0,0 +1,36 @@ +#!/bin/sh + +require() { + for cmd in "$@"; do + command -v "$cmd" >/dev/null 2>&1 || { + echo "doc: missing dependency: $cmd" >&2 + exit 1 + } + done +} + +require sioyek + +dir="$HOME/doc" +test -d "$dir" || exit + +if [ "$XDG_SESSION_TYPE" = x11 ]; then + require dmenu + picker() { dmenu -i -l 10 -p "Select file or folder: "; } +else + require rofi + picker() { rofi -dmenu -i -l 10 -p "Select file or folder"; } +fi + +while :; do + choice="$(find "$dir" -not -path "$dir/.*" -mindepth 1 -maxdepth 1 \( -type d -printf "%f/\n" -o -type f -printf "%f\n" \) | picker)" + + [ -n "$choice" ] || break + + if [ -d "$dir/${choice%/}" ]; then + dir="$dir/${choice%/}" + elif [ -f "$dir/$choice" ]; then + sioyek "$dir/$choice" & + break + fi +done diff --git a/scripts/hypr b/scripts/hypr deleted file mode 120000 index af972eb..0000000 --- a/scripts/hypr +++ /dev/null @@ -1 +0,0 @@ -/nix/store/i53klf9mw9p2k5nfawr6r2b0v3jyq1si-home-manager-files/.local/bin/scripts/hypr \ No newline at end of file diff --git a/scripts/hypr b/scripts/hypr new file mode 100755 index 0000000..bcb32c7 --- /dev/null +++ b/scripts/hypr @@ -0,0 +1,334 @@ +#!/bin/sh + +require() { + for cmd in "$@"; do + command -v "$cmd" >/dev/null 2>&1 || { + echo "hypr: missing dependency: $cmd" >&2 + exit 1 + } + done +} + +usage() { + cat < [app] [args...] + +Commands: + volume {up,down,toggle} Adjust volume accordingly and notify + brightness {up,down} Adjust brightness accordingly and notify + spawnfocus [args...] Focus existing window or spawn app with args + pull [app] Pull window to current workspace (picker if no app) + windowrules Apply dynamic window rules + exit Safely exit hyprland + +Options: + -h, --help Show this help message +EOF +} + +[ -z "$1" ] && { + usage + exit 1 +} + +cmd="$1" +shift + +case "$cmd" in +-h | --help) + usage + exit 0 + ;; +exit) + require hyprctl + pkill hypridle + pkill hyprpaper + hyprctl dispatch exit + exit 0 + ;; +brightness) + require brightnessctl notify-send + BRIGHT_STEP=5 + max_brightness="$(brightnessctl max)" + case "$1" in + up) + brightnessctl set "$BRIGHT_STEP"%+ + notify-send -u low -t 2500 -r 5555 "brightness $(brightnessctl get | awk -v max="$max_brightness" '{printf "%d%%", $1*100/max}')" + ;; + down) + brightnessctl set "$BRIGHT_STEP"%- + notify-send -u low -t 2500 -r 5555 "brightness $(brightnessctl get | awk -v max="$max_brightness" '{printf "%d%%", $1*100/max}')" + ;; + *) + echo "Invalid subcommand: $1" >&2 + exit 1 + ;; + esac + ;; +volume) + require pactl notify-send + SINK="@DEFAULT_SINK@" + VOL_STEP=5 + get_vol() { pactl get-sink-volume "$SINK" | awk 'NR==1{print $5+0}'; } + + case "$1" in + up) + vol=$(get_vol) + [ "$vol" -lt 100 ] && vol=$((vol + VOL_STEP)) + [ "$vol" -gt 100 ] && vol=100 + pactl set-sink-volume "$SINK" "${vol}%" + ;; + down) + vol=$(get_vol) + vol=$((vol - VOL_STEP)) + [ "$vol" -lt 0 ] && vol=0 + pactl set-sink-volume "$SINK" "${vol}%" + ;; + toggle) + pactl set-sink-mute "$SINK" toggle + ;; + *) + echo "Invalid subcommand: $1" >&2 + exit 1 + ;; + esac + + muted=$(pactl get-sink-mute "$SINK" | awk '{print $2}') + vol=$(get_vol) + [ "$vol" -gt 100 ] && vol=100 + + if [ "$muted" = "yes" ]; then + notify-send -u low -t 2500 -r 5556 "volume: ${vol}% (muted)" + else + notify-send -u low -t 2500 -r 5556 "volume: ${vol}%" + fi + ;; +pull) + require hyprctl jq rofi + APP="$1" + case "$APP" in + google-chrome | google-chrome-stable) CLASS="google-chrome" ;; + zen | zen-browser) CLASS="zen" ;; + chromium | ungoogled-chromium) CLASS="Chromium" ;; + firefox) CLASS="firefox" ;; + alacritty) CLASS="Alacritty" ;; + code | vscodium) CLASS="Code" ;; + signal-desktop | signal) CLASS="signal" ;; + telegram-desktop | telegram) CLASS="TelegramDesktop" ;; + ghostty) CLASS="com.mitchellh.ghostty" ;; + bitwarden-desktop | bitwarden) CLASS="Bitwarden" ;; + slack) CLASS="Slack" ;; + discord) CLASS="discord" ;; + vesktop) CLASS="vesktop" ;; + element-desktop | element) CLASS="element" ;; + *) CLASS="$APP" ;; + esac + + CUR_WS=$(hyprctl activeworkspace -j | jq -r '.id') + + CUR_ADDR=$(hyprctl -j activewindow | jq -r '.address // empty') + + WIN_ADDRS=$(hyprctl -j clients 2>/dev/null | jq -r --arg pat "$CLASS" ' + .[]? + | select( + (.class? | ascii_downcase | contains($pat | ascii_downcase)) or + (.initialClass? | ascii_downcase | contains($pat | ascii_downcase)) or + (.xdgTag? // "" | ascii_downcase | contains($pat | ascii_downcase)) + ) + | "\(.class)\t\(.title // "no title")\t\(.address)" + ') + + WIN_COUNT=$(echo "$WIN_ADDRS" | grep -c '^' || :) + + if [ "$WIN_COUNT" -eq 0 ]; then + exit 0 + fi + + if [ "$WIN_COUNT" -eq 1 ]; then + WIN_ADDR=$(echo "$WIN_ADDRS" | awk -F'\t' '{print $3}') + else + ROFI_LINES=$( + echo "$WIN_ADDRS" | + awk -F'\t' -v cur="$CUR_ADDR" ' + $3 != cur { printf "%s : %s\t%s\n", $1, $2, $3 } + ' + ) + + [ -z "$ROFI_LINES" ] && exit 0 + + SELECTED=$(echo "$ROFI_LINES" | rofi -dmenu -i -p "pull window") + + [ -z "$SELECTED" ] && exit 0 + + WIN_ADDR=$(echo "$SELECTED" | awk -F'\t' '{print $2}') + fi + + [ -z "$WIN_ADDR" ] && exit 0 + + hyprctl dispatch movetoworkspace "${CUR_WS},address:${WIN_ADDR}" + hyprctl dispatch focuswindow "address:${WIN_ADDR}" + ;; +spawnfocus) + require hyprctl jq rofi socat + WS="" + while [ $# -gt 0 ]; do + case "$1" in + --ws) + if [ $# -lt 2 ]; then + echo "Missing workspace number after --ws" + exit 1 + fi + WS="$2" + shift 2 + ;; + -*) + echo "Unknown option $1" + exit 1 + ;; + *) + break + ;; + esac + done + + APP="$1" + [ -z "$APP" ] && { + echo 'Specify an app' + exit 1 + } + shift + + case "$APP" in + google-chrome | google-chrome-stable) CLASS="google-chrome" ;; + chromium | ungoogled-chromium) CLASS="Chromium" ;; + zen | zen-browser) CLASS="zen" ;; + firefox) CLASS="firefox" ;; + alacritty) CLASS="Alacritty" ;; + code | vscodium) CLASS="Code" ;; + signal-desktop | signal) CLASS="signal" ;; + telegram-desktop | telegram) CLASS="TelegramDesktop" ;; + ghostty) CLASS="com.mitchellh.ghostty" ;; + bitwarden-desktop | bitwarden) CLASS="Bitwarden" ;; + slack) CLASS="Slack" ;; + discord) CLASS="discord" ;; + vesktop) CLASS="vesktop" ;; + element-desktop | element) CLASS="element" ;; + *) CLASS="$APP" ;; + esac + + WIN_ADDRS=$(hyprctl -j clients 2>/dev/null | jq -r --arg class "$CLASS" ' + .[]? | select( + ((.xdgTag // "") | ascii_downcase | contains($class | ascii_downcase)) or + ((.initialClass // "") | ascii_downcase | contains($class | ascii_downcase)) or + ((.class // "") | ascii_downcase | contains($class | ascii_downcase)) + ) | "\(.class)\t\(.title)\t\(.address)" + ') + + WIN_COUNT=$(echo "$WIN_ADDRS" | grep -c .) + + if [ "$WIN_COUNT" -eq 0 ]; then + WIN_ADDR="" + elif [ "$WIN_COUNT" -eq 1 ]; then + WIN_ADDR=$(echo "$WIN_ADDRS" | awk -F'\t' '{print $3}') + elif [ "$WIN_COUNT" -gt 1 ]; then + SELECTED=$(echo "$WIN_ADDRS" | awk -F'\t' '{print $1 ": " $2 "\t" $3}' | rofi -dmenu -i -p "select window") + + if [ -z "$SELECTED" ]; then + exit 0 + fi + + WIN_ADDR=$(echo "$SELECTED" | awk -F'\t' '{print $2}') + fi + if [ -n "$WIN_ADDR" ]; then + if [ -n "$WS" ]; then + WIN_WS=$(hyprctl clients -j | jq -r --arg addr "$WIN_ADDR" '.[] | select(.address == $addr) | .workspace.id') + + if [ "$WIN_WS" != "$WS" ]; then + hyprctl dispatch movetoworkspacesilent "$WS,address:$WIN_ADDR" + fi + + hyprctl dispatch workspace "$WS" + else + WIN_WS=$(hyprctl clients -j | jq -r --arg addr "$WIN_ADDR" '.[] | select(.address == $addr) | .workspace.id') + + hyprctl dispatch workspace "$WIN_WS" + fi + + hyprctl dispatch focuswindow "address:$WIN_ADDR" + else + EXISTING_HEX=$(hyprctl -j clients 2>/dev/null | jq -r --arg class "$CLASS" ' + .[]? | select( + ((.xdgTag // "") | ascii_downcase | contains($class | ascii_downcase)) or + ((.initialClass // "") | ascii_downcase | contains($class | ascii_downcase)) or + ((.class // "") | ascii_downcase | contains($class | ascii_downcase)) + ) | .address | ltrimstr("0x") + ' | tr '\n' ' ') + + if [ -n "$WS" ]; then + hyprctl dispatch workspace "$WS" + fi + + if [ $# -eq 0 ]; then + "$APP" & + else + "$APP" "$@" & + fi + + socat -u UNIX-CONNECT:"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" - | while read -r line; do + case "$line" in + openwindow*) + window_id=$(echo "$line" | cut -d'>' -f3 | cut -d',' -f1) + event_class=$(echo "$line" | cut -d'>' -f3 | cut -d',' -f3) + class_lower=$(echo "$event_class" | tr '[:upper:]' '[:lower:]') + target_lower=$(echo "$CLASS" | tr '[:upper:]' '[:lower:]') + case "$class_lower" in + *"$target_lower"*) + case " $EXISTING_HEX " in + *" $window_id "*) ;; + *) + addr="0x$window_id" + if [ -n "$WS" ]; then + hyprctl dispatch movetoworkspacesilent "$WS,address:$addr" + fi + hyprctl dispatch focuswindow "address:$addr" + break + ;; + esac + ;; + esac + ;; + esac + done + fi + ;; +windowrules) + require hyprctl socat + socat -u UNIX-CONNECT:"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" - | while IFS= read -r line; do + event="${line%%>>*}" + data="${line#*>>}" + case "$event" in + windowtitlev2) + window_id="${data%%,*}" + window_title="${data#*,}" + window_title=$(echo "$window_title" | tr '[:upper:]' '[:lower:]') + case "$window_title" in + *'extension: (bitwarden password manager) - bitwarden'*) + hyprctl --batch "dispatch setfloating address:0x$window_id; dispatch centerwindow address:0x$window_id" + ;; + *'sign in - google accounts '*) + hyprctl --batch "dispatch setfloating address:0x$window_id; \ + dispatch resizewindowpixel exact 30% 70%,address:0x$window_id; \ + dispatch centerwindow address:0x$window_id" + ;; + esac + ;; + esac + done + ;; + +*) + echo "Unknown subcommand: $cmd" + usage + exit 1 + ;; +esac diff --git a/scripts/mux b/scripts/mux deleted file mode 120000 index 733312c..0000000 --- a/scripts/mux +++ /dev/null @@ -1 +0,0 @@ -/nix/store/i53klf9mw9p2k5nfawr6r2b0v3jyq1si-home-manager-files/.local/bin/scripts/mux \ No newline at end of file diff --git a/scripts/mux b/scripts/mux new file mode 100755 index 0000000..019238e --- /dev/null +++ b/scripts/mux @@ -0,0 +1,241 @@ +#!/bin/sh + +require() { + for cmd in "$@"; do + command -v "$cmd" >/dev/null 2>&1 || { + echo "mux: missing dependency: $cmd" >&2 + exit 1 + } + done +} + +require tmux + +get_scope() { + _wname=$(tmux display-message -p '#{window_name}') + case "$_wname" in + ai@*|code@*|git@*|run@*|term@*|misc@*) printf '%s' "${_wname#*@}" ;; + *) printf '%s' "$_wname" ;; + esac +} + +spawn_or_focus() { + scope=$(get_scope) + name="${1}@${scope}" + cmd="$2" + + if tmux list-windows -F '#{window_name}' | grep -Fx "$name" >/dev/null; then + tmux select-window -t "${name}" + else + if [ -n "$cmd" ]; then + tmux new-window -c '#{pane_current_path}' -n "${name}" "$cmd" + else + tmux new-window -c '#{pane_current_path}' -n "${name}" + fi + fi +} + +pick_session() { + require fzf column + sel=$({ + printf 'name\twindows\tstatus\n' + tmux list-sessions -F '#{session_name} #{session_windows}w #{?session_attached,*,}' + } | column -t -s "$(printf '\t')" | + fzf --reverse --header-lines 1 --prompt 'select-session> ') + [ -n "$sel" ] && tmux switch-client -t "${sel%% *}" +} + +pick_window() { + require fzf column + sel=$({ + printf 'target\tname\tcommand\tpanes\n' + tmux list-windows -a -F '#{window_index}:#{session_name} #{window_name} #{pane_current_command} #{window_panes}p' + } | column -t -s "$(printf '\t')" | + fzf --reverse --header-lines 1 --prompt 'select-window> ') + sel="${sel%% *}" + [ -n "$sel" ] && tmux switch-client -t "${sel#*:}:${sel%%:*}" +} + +pick_pane() { + require fzf column + sel=$({ + printf 'target\tcommand\tpath\n' + tmux list-panes -a -F '#{window_index}.#{pane_index}:#{session_name} #{pane_current_command} #{pane_current_path}' | + sed "s|$HOME|~|g" + } | column -t -s "$(printf '\t')" | + fzf --reverse --header-lines 1 --prompt 'select-pane> ') + sel="${sel%% *}" + [ -n "$sel" ] && tmux switch-client -t "${sel#*:}:${sel%%:*}" +} + +target_pane() { + require fzf column + sel=$({ + printf 'target\tcommand\tpath\n' + tmux list-panes -a -F '#{window_index}.#{pane_index}:#{session_name} #{pane_current_command} #{pane_current_path}' | + sed "s|$HOME|~|g" + } | column -t -s "$(printf '\t')" | + fzf --reverse --header-lines 1 --prompt "$1> ") + sel="${sel%% *}" + [ -n "$sel" ] && printf '%s' "${sel#*:}:${sel%%:*}" +} + +confirm() { + printf '%s ' "$1" + old=$(stty -g) + stty raw -echo + c=$(dd bs=1 count=1 2>/dev/null) + stty "$old" + printf '\n' + [ "$c" = "y" ] +} + +case "$1" in +bar) + session=$(tmux display-message -p '#S') + [ "$session" ] || exit + if [ "$(tmux show-options -gv mouse)" = "on" ]; then + indicator='#{?pane_in_mode,[mouse#{@c}copy],[mouse]}' + else + indicator='#{?pane_in_mode,[copy],}' + fi + set -f + keys="H J K L" + bar_content="" + i=0 + total=$(tmux ls -F x | wc -l) + for line in $(tmux ls -F '#{session_id}:#{session_name}'); do + sid="${line%%:*}" + sname="${line#*:}" + if [ $i -lt 4 ]; then + key=$(echo "$keys" | cut -d' ' -f $((i + 1))) + elif [ $i -eq $((total - 1)) ]; then + key='$' + else + key='?' + fi + star="" + [ "$sname" = "$session" ] && star="*" + [ -n "$bar_content" ] && bar_content="$bar_content │ " + bar_content="$bar_content#[range=session|${sid}]$key:$sname$star#[norange]" + i=$((i + 1)) + done + set +f + left='#[align=left list=on] #{W:#[range=window|#{window_index}]#{window_index}:#{window_name}#{window_flags}#[norange]#{?window_end_flag,, │ }}#[nolist]' + right="#[align=right]$indicator $bar_content" + tmux set -g 'status-format[0]' "$left$right" + ;; +switch) + session="$(tmux ls -F '#S' | tail -n "+$(($2 + 1))" | head -1)" + tmux switch -t "$session" + ;; +pick-session) + pick_session + ;; +pick-window) + pick_window + ;; +pick-pane) + pick_pane + ;; +ai) + require claude + spawn_or_focus ai 'claude' + ;; +code) + require nvim + spawn_or_focus code 'nvim -c "lua require([[config.tmux]]).run([[nvim]])"' + ;; +git) + require nvim git + pane_path=$(tmux display-message -p '#{pane_current_path}') + if ! git -C "$pane_path" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + tmux display-message "Not a git repository" + else + spawn_or_focus git 'nvim -c "lua require([[config.tmux]]).run([[git]])"' + fi + ;; +run) + require nvim + spawn_or_focus run 'nvim -c "lua require([[config.tmux]]).run([[run]])"' + ;; +term) + spawn_or_focus term + ;; +misc) + spawn_or_focus misc + ;; +cmd) + require fzf + result=$(tmux list-commands | + sed 's/ / /' | + fzf --reverse --prompt ':' --print-query \ + --delimiter '\t' --with-nth '1,2' --accept-nth '1' \ + --bind 'ctrl-y:transform-query(echo {1})+disable-search') + rc=$? + query=$(printf '%s' "$result" | head -1) + action=$(printf '%s' "$result" | sed -n '2p') + [ $rc -eq 130 ] && exit + if [ -n "$action" ]; then + case "$query" in + "$action "*) + tmux $query + exit + ;; + esac + case "$action" in + switch-client) pick_session ;; + select-window) pick_window ;; + select-pane) pick_pane ;; + rename-window) + cur=$(tmux display-message -p '#{window_name}') + printf 'name [%s]: ' "$cur" + read -r name + [ -n "$name" ] && tmux rename-window "$name" + ;; + rename-session) + cur=$(tmux display-message -p '#S') + printf 'name [%s]: ' "$cur" + read -r name + [ -n "$name" ] && tmux rename-session "$name" + ;; + join-pane) + target=$(target_pane 'join-pane') + [ -n "$target" ] && tmux join-pane -s "$target" + ;; + swap-pane) + target=$(target_pane 'swap-pane') + [ -n "$target" ] && tmux swap-pane -t "$target" + ;; + select-layout) + layout=$(printf '%s\n' \ + 'even-horizontal' \ + 'even-vertical' \ + 'main-horizontal' \ + 'main-vertical' \ + 'tiled' | + fzf --reverse --prompt 'layout> ') + [ -n "$layout" ] && tmux select-layout "$layout" + ;; + kill-pane) + confirm 'kill-pane? [y/N]:' && tmux kill-pane + ;; + kill-window) + confirm "kill-window \"$(tmux display-message -p '#{window_name}')\"? [y/N]:" && tmux kill-window + ;; + kill-session) + confirm "kill-session \"$(tmux display-message -p '#S')\"? [y/N]:" && tmux kill-session + ;; + kill-server) + confirm 'kill-server? [y/N]:' && tmux kill-server + ;; + *) tmux "$action" ;; + esac + elif [ -n "$query" ]; then + tmux $query + fi + ;; +*) + tmux attach + ;; +esac diff --git a/scripts/pipes b/scripts/pipes deleted file mode 120000 index c009f8f..0000000 --- a/scripts/pipes +++ /dev/null @@ -1 +0,0 @@ -/nix/store/i53klf9mw9p2k5nfawr6r2b0v3jyq1si-home-manager-files/.local/bin/scripts/pipes \ No newline at end of file diff --git a/scripts/pipes b/scripts/pipes new file mode 100755 index 0000000..5ee0b51 --- /dev/null +++ b/scripts/pipes @@ -0,0 +1,227 @@ +#!/usr/bin/env bash +# pipes.sh: Animated pipes terminal screensaver. +# https://github.com/pipeseroni/pipes.sh +# +# Copyright (c) 2015-2018 Pipeseroni/pipes.sh contributors +# Copyright (c) 2013-2015 Yu-Jie Lin +# Copyright (c) 2010 Matthew Simpson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +VERSION=1.3.0 + +M=32768 # Bash RANDOM maximum + 1 +p=1 # number of pipes +f=75 # frame rate +s=13 # probability of straight fitting +r=2000 # characters limit +t=0 # iteration counter for -r character limit +w=80 # terminal size +h=24 + +# ab -> sets[][idx] = a*4 + b +# 0: up, 1: right, 2: down, 3: left +# 00 means going up , then going up -> ┃ +# 12 means going right, then going down -> ┓ +sets=( + "┃┏ ┓┛━┓ ┗┃┛┗ ┏━" + "│╭ ╮╯─╮ ╰│╯╰ ╭─" + "│┌ ┐┘─┐ └│┘└ ┌─" + "║╔ ╗╝═╗ ╚║╝╚ ╔═" + "|+ ++-+ +|++ +-" + "|/ \/-\ \|/\ /-" + ".. .... .... .." + ".o oo.o o.oo o." + "-\ /\|/ /-\/ \|" + "╿┍ ┑┚╼┒ ┕╽┙┖ ┎╾" + "████▀▀███▀█▀▀██▀" +) + +# pipes' +x=() # current position +y=() +l=() # current directions + # 0: up, 1: right, 2: down, 3: left +n=() # new directions +v=() # current types +c=() # current color + +# selected pipes' +V=() # types (indexes to sets[]) +C=() # colors +VN=0 # number of selected types +CN=0 # number of selected colors + +# switches +RNDSTART=0 # randomize starting position and direction +BOLD=1 +NOCOLOR=0 +KEEPCT=0 # keep pipe color and type + + +parse() { + OPTIND=1 + while getopts "p:t:c:f:s:r:RBCKhv" arg; do + case $arg in + p) ((p = (OPTARG > 0) ? OPTARG : p));; + t) + if [[ "$OPTARG" = c???????????????? ]]; then + V+=(${#sets[@]}) + sets+=("${OPTARG:1}") + else + ((OPTARG >= 0 && OPTARG < ${#sets[@]})) && V+=($OPTARG) + fi + ;; + c) [[ $OPTARG =~ ^[0-7]$ ]] && C+=($OPTARG);; + f) ((f = (OPTARG > 19 && OPTARG < 101) ? OPTARG : f));; + s) ((s = (OPTARG > 4 && OPTARG < 16) ? OPTARG : s));; + r) ((r = (OPTARG >= 0) ? OPTARG : r));; + R) RNDSTART=1;; + B) BOLD=0;; + C) NOCOLOR=1;; + K) KEEPCT=1;; + h) echo -e "Usage: $(basename $0) [OPTION]..." + echo -e "Animated pipes terminal screensaver.\n" + echo -e " -p [1-]\tnumber of pipes (D=1)." + echo -e " -t [0-$((${#sets[@]} - 1))]\ttype of pipes, can be used more than once (D=0)." + echo -e " -c [0-7]\tcolor of pipes, can be used more than once (D=1 2 3 4 5 6 7 0)." + echo -e " -t c[16 chars]\tcustom type of pipes." + echo -e " -f [20-100]\tframerate (D=75)." + echo -e " -s [5-15]\tprobability of a straight fitting (D=13)." + echo -e " -r LIMIT\treset after x characters, 0 if no limit (D=2000)." + echo -e " -R \t\trandomize starting position and direction." + echo -e " -B \t\tno bold effect." + echo -e " -C \t\tno color." + echo -e " -K \t\tpipes keep their color and type when hitting the screen edge." + echo -e " -h\t\thelp (this screen)." + echo -e " -v\t\tprint version number.\n" + exit 0;; + v) echo "$(basename -- "$0") $VERSION" + exit 0 + esac + done + + # set default values if not by options + ((${#V[@]})) || V=(0) + VN=${#V[@]} + ((${#C[@]})) || C=(1 2 3 4 5 6 7 0) + CN=${#C[@]} +} + + +cleanup() { + # clear out standard input + read -t 0.001 && cat /dev/null + + # terminal has no smcup and rmcup capabilities + ((FORCE_RESET)) && reset && exit 0 + + tput reset # fix for konsole, see pipeseroni/pipes.sh#43 + tput rmcup + tput cnorm + # stty echo + ((NOCOLOR)) && echo -ne '\e[0m' + exit 0 +} + + +resize() { + w=$(tput cols) h=$(tput lines) +} + + +init() { + local i + + resize + trap resize SIGWINCH + ci=$((KEEPCT ? 0 : CN * RANDOM / M)) + vi=$((KEEPCT ? 0 : VN * RANDOM / M)) + for ((i = 0; i < p; i++)); {(( + n[i] = 0, + l[i] = RNDSTART ? RANDOM % 4 : 0, + x[i] = RNDSTART ? w * RANDOM / M : w / 2, + y[i] = RNDSTART ? h * RANDOM / M : h / 2, + c[i] = C[ci], + v[i] = V[vi], + ci = (ci + 1) % CN, + vi = (vi + 1) % VN + ));} + + # stty -echo + tput smcup || FORCE_RESET=1 + tput civis + tput clear + trap cleanup HUP TERM +} + + +main() { + local i + + parse "$@" + init "$@" + + # any key press exits the loop and this script + trap 'break 2' INT + while REPLY=; do + read -t 0.0$((1000 / f)) -n 1 2>/dev/null + case "$REPLY" in + P) ((s = s < 15 ? s + 1 : s));; + O) ((s = s > 3 ? s - 1 : s));; + F) ((f = f < 100 ? f + 1 : f));; + D) ((f = f > 20 ? f - 1 : f));; + B) ((BOLD = (BOLD + 1) % 2));; + C) ((NOCOLOR = (NOCOLOR + 1) % 2));; + K) ((KEEPCT = (KEEPCT + 1) % 2));; + ?) break;; + esac + for ((i = 0; i < p; i++)); do + # New position: + # l[] direction = 0: up, 1: right, 2: down, 3: left + ((l[i] % 2)) && ((x[i] += -l[i] + 2, 1)) || ((y[i] += l[i] - 1)) + + # Loop on edges (change color on loop): + ((!KEEPCT && (x[i] >= w || x[i] < 0 || y[i] >= h || y[i] < 0))) \ + && ((c[i] = C[CN * RANDOM / M], v[i] = V[VN * RANDOM / M])) + ((x[i] = (x[i] + w) % w)) + ((y[i] = (y[i] + h) % h)) + + # New random direction: + ((n[i] = s * RANDOM / M - 1)) + ((n[i] = (n[i] > 1 || n[i] == 0) ? l[i] : l[i] + n[i])) + ((n[i] = (n[i] < 0) ? 3 : n[i] % 4)) + + # Print: + tput cup ${y[i]} ${x[i]} + echo -ne "\e[${BOLD}m" + ((NOCOLOR)) && echo -ne "\e[0m" || echo -ne "\e[3${c[i]}m" + echo -n "${sets[v[i]]:l[i]*4+n[i]:1}" + l[i]=${n[i]} + done + ((r > 0 && t * p >= r)) && tput reset && tput civis && t=0 || ((t++)) + done + + cleanup +} + + +main "$@" + diff --git a/scripts/theme b/scripts/theme deleted file mode 120000 index 97b76dc..0000000 --- a/scripts/theme +++ /dev/null @@ -1 +0,0 @@ -/nix/store/i53klf9mw9p2k5nfawr6r2b0v3jyq1si-home-manager-files/.local/bin/scripts/theme \ No newline at end of file diff --git a/scripts/theme b/scripts/theme new file mode 100755 index 0000000..4f0e633 --- /dev/null +++ b/scripts/theme @@ -0,0 +1,134 @@ +#!/bin/sh + +require() { + for cmd in "$@"; do + command -v "$cmd" >/dev/null 2>&1 || { + echo "theme: missing dependency: $cmd" >&2 + exit 1 + } + done +} + +themes="daylight +midnight" + +as_list="$(printf "%s\n" "$themes" | awk 'NF{printf "\"%s\",", $0}' | sed 's/,$//')" + +case "$(uname)" in +Linux) + if [ -n "$1" ]; then + theme="$1" + else + if [ "$XDG_SESSION_TYPE" = "wayland" ]; then + require rofi + theme="$(printf "%s\n" "$themes" | rofi -dmenu -p 'theme')" + else + require dmenu + theme="$(printf "%s\n" "$themes" | dmenu -p 'select theme: ')" + fi + fi + ;; +Darwin) + if [ -n "$1" ]; then + theme="$1" + else + theme="$( + osascript </dev/null 2>&1; then + case "$theme" in + midnight) + gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' + ;; + daylight) + gsettings set org.gnome.desktop.interface color-scheme 'prefer-light' + ;; + *) ;; + esac + fi + + [ "$XDG_SESSION_TYPE" = "wayland" ] && { + if [ "$XDG_CURRENT_DESKTOP" = 'Hyprland' ]; then + current_theme=$(readlink "$XDG_CONFIG_HOME/hypr/theme.conf" 2>/dev/null | awk -F/ '{print $NF}' | sed 's/\.conf$//') + if [ "$current_theme" != "$theme" ]; then + ln -sf "$XDG_CONFIG_HOME/hypr/themes/$theme.conf" "$XDG_CONFIG_HOME/hypr/theme.conf" + ln -sf "$XDG_CONFIG_HOME/waybar/themes/$theme.css" "$XDG_CONFIG_HOME/waybar/themes/theme.css" + hyprctl reload + pkill -USR1 waybar + pkill -USR1 waybar + fi + elif [ "$XDG_CURRENT_DESKTOP" = 'sway' ]; then + current_theme=$(readlink "$XDG_CONFIG_HOME/sway/themes/theme" 2>/dev/null | sed 's|.*/||') + if [ "$current_theme" != "$theme" ]; then + test -f "$XDG_CONFIG_HOME/sway/themes/theme" && ln -sf "$XDG_CONFIG_HOME/sway/themes/$theme" "$XDG_CONFIG_HOME/sway/themes/theme" + swaymsg reload + fi + fi + } + + test -d "$XDG_CONFIG_HOME/rofi/themes" && ln -sf "$XDG_CONFIG_HOME/rofi/themes/$theme.rasi" "$XDG_CONFIG_HOME/rofi/themes/theme.rasi" + ;; +Darwin) + case "$theme" in + daylight) + osascript -e 'tell app "System Events" to tell appearance preferences to set dark mode to false' 2>/dev/null || true + ;; + midnight) + osascript -e 'tell app "System Events" to tell appearance preferences to set dark mode to true' 2>/dev/null || true + ;; + esac + ;; +esac + +test -d "$XDG_CONFIG_HOME/fzf/themes" && ln -sf "$XDG_CONFIG_HOME/fzf/themes/$theme" "$XDG_CONFIG_HOME/fzf/themes/theme" +test -d "$XDG_CONFIG_HOME/rg/themes" && ln -sf "$XDG_CONFIG_HOME/rg/themes/$theme" "$XDG_CONFIG_HOME/rg/themes/theme" +test -d "$XDG_CONFIG_HOME/sioyek/themes" && ln -sf "$XDG_CONFIG_HOME/sioyek/themes/$theme.config" "$XDG_CONFIG_HOME/sioyek/themes/theme.config" +test -d "$XDG_CONFIG_HOME/task/themes" && ln -sf "$XDG_CONFIG_HOME/task/themes/$theme.theme" "$XDG_CONFIG_HOME/task/themes/theme.theme" + +if command -v claude >/dev/null 2>&1; then + CLAUDE_CONFIG="${CLAUDE_CONFIG_DIR:-$HOME}/.claude.json" + claude_theme='light' + case "$theme" in + daylight) + claude_theme='light' + ;; + midnight) + claude_theme='dark' + ;; + esac + test -f "$CLAUDE_CONFIG" && jq ".theme=\"$claude_theme\"" "$CLAUDE_CONFIG" >"$CLAUDE_CONFIG.tmp" && mv "$CLAUDE_CONFIG.tmp" "$CLAUDE_CONFIG" +fi + +test -f ~/.zshenv && sed -i "s|^\(export THEME=\).*|\1$theme|" ~/.zshenv +[ -n "$TMUX" ] && tmux setenv -g THEME "$theme" + +for socket in /tmp/nvim-*.sock; do + test -S "$socket" && nvim --server "$socket" --remote-expr "luaeval('require(\"config.fzf_reload\").reload()')" 2>/dev/null || true +done diff --git a/scripts/wp b/scripts/wp deleted file mode 120000 index e8a7196..0000000 --- a/scripts/wp +++ /dev/null @@ -1 +0,0 @@ -/nix/store/i53klf9mw9p2k5nfawr6r2b0v3jyq1si-home-manager-files/.local/bin/scripts/wp \ No newline at end of file diff --git a/scripts/wp b/scripts/wp new file mode 100755 index 0000000..40ade30 --- /dev/null +++ b/scripts/wp @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 + +import os +import random +import subprocess +import sys + +try: + from PIL import Image, ImageDraw +except ImportError: + print("wp: missing dependency: pillow (pip install pillow)", file=sys.stderr) + sys.exit(1) + +HOME = os.environ["HOME"] +DIR = f"{HOME}/img/screen" +os.makedirs(DIR, exist_ok=True) + + +def get_resolution(): + output = "1920x1080" + if "WAYLAND_DISPLAY" in os.environ: + desktop = os.environ.get("XDG_CURRENT_DESKTOP", "") + if desktop == "Hyprland": + output = ( + subprocess.check_output( + "hyprctl monitors -j | jq -r '.[] | select(.focused==true) | .width, .height' | paste -sd x -", + shell=True, + ) + .decode() + .strip() + ) + elif desktop == "sway": + output = ( + subprocess.check_output( + "swaymsg -t get_outputs -r | jq -r '.[] | select(.active==true) | .current_mode.width,.current_mode.height' | paste -sd x -", + shell=True, + ) + .decode() + .strip() + ) + else: + output = ( + subprocess.check_output( + "xrandr | grep '*' | awk '{print $1}'", shell=True + ) + .decode() + .strip() + ) + return map(int, output.split("x")) + + +def lock(): + W, H = get_resolution() + UNIT = 16 + grid_w = W // UNIT + grid_h = H // UNIT + + bg_color = tuple(random.randint(0, 255) for _ in range(3)) + img = Image.new("RGB", (W, H), bg_color) + draw = ImageDraw.Draw(img) + + MU = 0.8 + SIGMA = 0.7 + SCALE_FACTOR = 1.8 + + S = {(x, y) for x in range(grid_w) for y in range(grid_h)} + N = len(S) + + while S: + if random.random() < 1 / N: + break + gx, gy = S.pop() + w_units = max( + 1, + min( + grid_w - gx, + int(round(random.lognormvariate(MU, SIGMA) * SCALE_FACTOR)), + ), + ) + h_units = max( + 1, + min( + grid_h - gy, + int(round(random.lognormvariate(MU, SIGMA) * SCALE_FACTOR)), + ), + ) + color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + + dx = random.choice([1, -1]) + dy = random.choice([1, -1]) + + gx_end = gx + dx * (w_units - 1) + gy_end = gy + dy * (h_units - 1) + + x0, x1 = sorted([gx, gx_end]) + y0, y1 = sorted([gy, gy_end]) + + draw.rectangle( + [x0 * UNIT, y0 * UNIT, (x1 + 1) * UNIT - 1, (y1 + 1) * UNIT - 1], + fill=color, + ) + + img.save(f"{DIR}/lock.jpg", quality=95) + + +def wall(): + W, H = get_resolution() + + colors = [ + "#aa0000", + "#ff3333", + "#ff7777", + "#ffbb55", + "#ffcc88", + "#ff88ff", + "#aaaaff", + "#77ddbb", + "#77ff77", + "#cccccc", + ] + + colors.reverse() + + img = Image.new("RGB", (W, H), "white") + draw = ImageDraw.Draw(img) + + num_bars = len(colors) + + for i, color in enumerate(reversed(colors)): + top = round(i * H / num_bars) + bottom = round((i + 1) * H / num_bars) + draw.rectangle([0, top, W, bottom], fill=color) + + img.save(f"{DIR}/wallpaper.jpg", quality=95) + + +if len(sys.argv) < 2: + print("Usage: wp {lock|wall}", file=sys.stderr) + sys.exit(1) + +cmd = sys.argv[1] +if cmd == "lock": + lock() +elif cmd == "wall": + wall() +else: + print("Usage: wp {lock|wall}", file=sys.stderr) + sys.exit(1) diff --git a/scripts/x b/scripts/x deleted file mode 120000 index 5839a0b..0000000 --- a/scripts/x +++ /dev/null @@ -1 +0,0 @@ -/nix/store/i53klf9mw9p2k5nfawr6r2b0v3jyq1si-home-manager-files/.local/bin/scripts/x \ No newline at end of file diff --git a/scripts/x b/scripts/x new file mode 100755 index 0000000..2e74ad7 --- /dev/null +++ b/scripts/x @@ -0,0 +1,68 @@ +#!/bin/sh + +require() { + for cmd in "$@"; do + command -v "$cmd" >/dev/null 2>&1 || { + echo "x: missing dependency: $cmd" >&2 + exit 1 + } + done +} + +cmd="$1"; shift + +case "$cmd" in +setup) + require xrdb xset xmodmap xss-lock slock + xrdb -merge "$XDG_CONFIG_HOME"/X11/xresources."$THEME" + xset b off + xset m 0 + xset r rate 300 50 + xset s 300 + xset dpms 420 540 720 + xmodmap "$XDG_CONFIG_HOME"/X11/xmodmap + xss-lock -- slock & + ;; +bg) + require xrandr feh + randr="$(xrandr | rg ' connected ')" + mons="$(echo "$randr" | wc -l)" + wpdir="$HOME"/img/wp + + [ "$1" ] && bgone="$1" || bgone="$wpdir"/one/cliff.jpg + cmd="feh --no-fehbg --bg-fill $bgone" + + if [ "$mons" = 2 ]; then + [ "$2" ] && bgtwo="$2" || bgtwo="$wpdir"/two/lilies.jpg + cmd="$cmd --bg-fill $bgtwo" + else + cmd="feh --no-fehbg --bg-fill $bgone" + fi + + eval "$cmd" + ;; +mon) + require xrandr + mons="$(xrandr | rg --no-config ' connected ' | awk '{ print $1 }')" + + one="$(echo "$mons" | head -n 1)" + two="$(echo "$mons" | tail -n 1)" + case "$two" in + *None* | "$one") + unset two + ;; + esac + + xrandr --auto + + xrandr --output "$one" --primary --mode 1920x1200 --scale 1x1 --set TearFree on --dpi 161.73 + + [ -z "$two" ] && exit + + xrandr --output "$one" --pos 960x2160 --output "$two" --scale 1.5x1.5 --pos 0x0 + ;; +*) + echo "Usage: x {setup|bg|mon}" >&2 + exit 1 + ;; +esac