nix/scripts/hypr

326 lines
8.8 KiB
Bash
Executable file

#!/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 <<EOF
Usage: hypr <subcommand> [app] [args...]
Commands:
volume {up,down,toggle} Adjust volume accordingly and notify
brightness {up,down} Adjust brightness accordingly and notify
spawnfocus <app> [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 -t 2500 -r 5555 "brightness $(brightnessctl get | awk -v max="$max_brightness" '{printf "%d%%", $1*100/max}')"
;;
down)
brightnessctl set "$BRIGHT_STEP"%-
notify-send -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 wpctl notify-send
SINK="@DEFAULT_AUDIO_SINK@"
VOL_STEP=5
get_vol() { wpctl get-volume "$SINK" | awk '{printf "%d", $2 * 100}'; }
case "$1" 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 "Invalid subcommand: $1" >&2
exit 1
;;
esac
vol=$(get_vol)
if wpctl get-volume "$SINK" | grep -q MUTED; then
notify-send -t 2500 -r 5556 "volume: ${vol}% (muted)"
else
notify-send -t 2500 -r 5556 "volume: ${vol}%"
fi
;;
pull)
require hyprctl jq fuzzel
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" | fuzzel --dmenu --prompt="pull: ")
[ -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 fuzzel 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}' | fuzzel --dmenu --prompt="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:]')
# NOTE: has been buggy recently
# 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