#!/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 next) if [ "${XDG_SESSION_TYPE:-}" = "wayland" ]; then if [ "$XDG_CURRENT_DESKTOP" = "Hyprland" ]; then hyprctl switchxkblayout current next elif [ "$XDG_CURRENT_DESKTOP" = "sway" ]; then swaymsg input type:keyboard xkb_switch_layout next fi else current="$(setxkbmap -query | awk '/variant/{print $2}')" case "$current" in "") setxkbmap -layout us -variant dvorak ;; dvorak) setxkbmap -layout us -variant colemak ;; *) setxkbmap -layout us ;; esac fi ;; pick) require fuzzel choice=$(printf 'QWERTY\nDvorak\nColemak' | fuzzel --dmenu --prompt="keyboard: " --lines=3 --no-icons) [ -z "$choice" ] && exit 0 case "$choice" in QWERTY) variant="" ;; Dvorak) variant="dvorak" ;; Colemak) variant="colemak" ;; esac if [ "$XDG_CURRENT_DESKTOP" = "Hyprland" ]; then hyprctl keyword input:kb_layout "us,us,us" hyprctl keyword input:kb_variant ",$variant" hyprctl switchxkblayout current 1 fi ;; *) echo "Usage: ctl keyboard {next|pick}" >&2 exit 1 ;; esac ;; audio) require wpctl pw-dump jq case "$2" in out) sinks="$(pw-dump | jq -r '.[] | select(.info.props."media.class" == "Audio/Sink") | "\(.id)\t\(.info.props."node.description" // .info.props."node.name" // "unknown")"')" [ -z "$sinks" ] && exit 0 if [ "$XDG_SESSION_TYPE" = x11 ]; then choice="$(printf "%s\n" "$sinks" | awk -F'\t' '{print $1": "$2}' | dmenu -i -p "select sink:" | cut -d: -f1)" else count=$(printf "%s\n" "$sinks" | wc -l) choice="$(printf "%s\n" "$sinks" | awk -F'\t' '{print $1": "$2}' | fuzzel --dmenu --prompt="sink: " --lines="$count" | cut -d: -f1)" fi [ "$choice" ] && wpctl set-default "$choice" ;; in) sources="$(pw-dump | jq -r '.[] | select(.info.props."media.class" == "Audio/Source") | "\(.id)\t\(.info.props."node.description" // .info.props."node.name" // "unknown")"')" [ -z "$sources" ] && exit 0 if [ "$XDG_SESSION_TYPE" = x11 ]; then choice="$(printf "%s\n" "$sources" | awk -F'\t' '{print $1": "$2}' | dmenu -i -p "select source:" | cut -d: -f1)" else count=$(printf "%s\n" "$sources" | wc -l) choice="$(printf "%s\n" "$sources" | awk -F'\t' '{print $1": "$2}' | fuzzel --dmenu --prompt="source: " --lines="$count" | cut -d: -f1)" fi [ "$choice" ] && wpctl set-default "$choice" ;; *) echo "Usage: ctl audio {in|out}" >&2 exit 1 ;; esac ;; brightness) require brightnessctl notify-send BRIGHT_STEP=5 case "$2" in up) brightnessctl set "$BRIGHT_STEP"%+ ;; down) brightnessctl set "$BRIGHT_STEP"%- ;; *) echo "Usage: ctl brightness {up|down}" >&2; exit 1 ;; esac pct=$(awk -v cur="$(brightnessctl get)" -v max="$(brightnessctl max)" 'BEGIN { printf "%d", cur * 100 / max }') filled=$((pct / 5)) empty=$((20 - filled)) bar="" i=0; while [ "$i" -lt "$filled" ]; do bar="${bar}━"; i=$((i + 1)); done i=0; while [ "$i" -lt "$empty" ]; do bar="${bar}─"; i=$((i + 1)); done notify-send -a ctl -t 2500 -r 5555 "󰃠 $bar" ;; volume) require wpctl notify-send SINK="@DEFAULT_AUDIO_SINK@" VOL_STEP=5 get_vol() { wpctl get-volume "$SINK" | awk '{printf "%d", $2 * 100}'; } case "$2" in up) wpctl set-volume "$SINK" "${VOL_STEP}%+" --limit 1.0 ;; down) wpctl set-volume "$SINK" "${VOL_STEP}%-" ;; toggle) wpctl set-mute "$SINK" toggle ;; *) echo "Usage: ctl volume {up|down|toggle}" >&2; exit 1 ;; esac vol=$(get_vol) filled=$((vol / 5)) empty=$((20 - filled)) bar="" i=0; while [ "$i" -lt "$filled" ]; do bar="${bar}━"; i=$((i + 1)); done i=0; while [ "$i" -lt "$empty" ]; do bar="${bar}─"; i=$((i + 1)); done if wpctl get-volume "$SINK" | grep -q MUTED; then icon="󰖁" elif [ "$vol" -le 33 ]; then icon="󰕿" elif [ "$vol" -le 66 ]; then icon="󰖀" else icon="󰕾" fi notify-send -a ctl -t 2500 -r 5556 "$icon $bar" ;; wifi) case "$2" in pick) require fuzzel station=$(iwctl device list 2>/dev/null | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' | awk '/station/{print $1}') [ -z "$station" ] && { echo "ctl: no wifi device found" >&2; exit 1; } iwctl station "$station" scan 2>/dev/null sleep 1 networks=$(iwctl station "$station" get-networks 2>/dev/null | \ sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' | \ awk 'NR>4 && /\S/' | \ sed 's/^[[:space:]>]*//' | \ sed 's/[[:space:]]\{3,\}.*//' | \ sed 's/[[:space:]]*$//' | \ grep -v '^-') [ -z "$networks" ] && exit 0 count=$(printf '%s\n' "$networks" | wc -l) ssid=$(printf '%s\n' "$networks" | fuzzel --dmenu --prompt="wifi: " --no-icons --lines="$count") [ -z "$ssid" ] && exit 0 iwctl station "$station" connect "$ssid" ;; *) echo "Usage: ctl wifi {pick}" >&2 exit 1 ;; esac ;; power) require fuzzel lock="󰌾 Lock" suspend="󰒲 Suspend" logout="󰍃 Logout" reboot="󰜉 Reboot" shutdown="󰐥 Shutdown" chosen="$(printf '%s\n' "$lock" "$suspend" "$logout" "$reboot" "$shutdown" | fuzzel --dmenu --hide-prompt --lines=5 --width=20 --no-icons)" case "$chosen" in "$lock") hyprlock ;; "$suspend") systemctl suspend ;; "$logout") hyprctl dispatch exit ;; "$reboot") systemctl reboot ;; "$shutdown") systemctl poweroff ;; esac ;; *) echo "Usage: ctl {screenshot|ocr|keyboard|audio|wifi|brightness|volume|power}" >&2 exit 1 ;; esac