Files
dotfiles/.config/hypr/scripts/monitor-toggle.sh

258 lines
6.8 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
PRIMARY="eDP-2"
RIGHT_EXTERNAL_NAME="DP-2"
MIDDLE_EXTERNAL_NAME="HDMI-A-1"
# Exact enable lines (from your config)
EXT1='desc:Samsung Electric Company S24F350 H4ZR302705, highres@highrr, auto-right, 1'
EXT2='desc:Samsung Electric Company S24F350 H4ZK111233, highres@highrr, auto-right, 1, transform, 1'
# In dual mode, which one do we prefer?
DUAL_MAIN="$EXT1"
# Optional: restart Waybar after layout changes (0=off, 1=on)
RESTART_WAYBAR=1
# Dock/MST settle timing (tune if needed)
SETTLE_SECS=1.0
DPMS_KICK_RETRIES=2
DPMS_KICK_SLEEP=0.35
notify() {
if command -v notify-send >/dev/null 2>&1; then
notify-send -a "Hyprland" "$1" "${2:-}"
else
[ -n "${2:-}" ] && printf '%s: %s\n' "$1" "$2" >&2 || printf '%s\n' "$1" >&2
fi
}
hypr() {
local out
if ! out="$(hyprctl "$@" 2>&1)"; then
notify "hyprctl failed" "$out"
exit 1
fi
printf '%s' "$out"
}
hypr_batch() {
local out
if ! out="$(hyprctl --batch "$1" 2>&1)"; then
notify "hyprctl --batch failed" "$out"
exit 1
fi
printf '%s' "$out"
}
restart_waybar() {
[ "${RESTART_WAYBAR:-0}" -eq 1 ] || return 0
if pgrep -x waybar >/dev/null 2>&1; then
pkill -SIGUSR2 waybar >/dev/null 2>&1 || true
fi
}
jqok() { command -v jq >/dev/null 2>&1; }
monjson() { hyprctl -j monitors 2>/dev/null || true; }
sleep_s() { python - <<PY 2>/dev/null || sleep 1
import time
time.sleep(float("$1"))
PY
}
# --- monitor discovery helpers ---
enabled_monitor_names() {
if jqok; then
monjson | jq -r '.[] | select(.disabled != true) | .name'
else
# Fallback is weaker; jq is strongly recommended
hyprctl monitors | awk '
/^Monitor /{name=$2}
/disabled: false/{print name}
'
fi
}
focused_monitor_name() {
if jqok; then
monjson | jq -r '.[] | select(.focused==true) | .name // empty'
else
hyprctl monitors | awk '
/^Monitor /{name=$2}
/focused: yes/{print name; exit}
'
fi
}
external_enabled() {
local n
while IFS= read -r n; do
[ "$n" != "$PRIMARY" ] && return 0
done < <(enabled_monitor_names)
return 1
}
# We dont try to parse desc lines; we just treat them as “enable rules”
enable_rule() {
local rule="$1"
[ -n "$rule" ] && printf 'keyword monitor %s; ' "$rule"
}
disable_by_name() {
local name="$1"
[ -n "$name" ] && printf 'keyword monitor %s, disable; ' "$name"
}
# Disable all *currently enabled* externals by name (minimal churn)
batch_disable_enabled_externals() {
local batch="" n=""
while IFS= read -r n; do
[ "$n" = "$PRIMARY" ] && continue
batch+="$(disable_by_name "$n")"
done < <(enabled_monitor_names)
printf '%s' "$batch"
}
# Dock settle wait: give MST/alt-mode time to re-enumerate before we apply rules / kick DPMS
dock_settle() {
sleep_s "$SETTLE_SECS"
}
# DPMS kick for all enabled externals (works well for dock hotplug weirdness)
kick_externals() {
local names=() n=""
if jqok; then
mapfile -t names < <(monjson | jq -r --arg P "$PRIMARY" '
.[] | select(.name != $P and (.disabled != true)) | .name
')
else
mapfile -t names < <(enabled_monitor_names | awk -v P="$PRIMARY" '$0!=P')
fi
[ "${#names[@]}" -eq 0 ] && return 0
for _ in $(seq 1 "$DPMS_KICK_RETRIES"); do
for n in "${names[@]}"; do hypr dispatch dpms off "$n" >/dev/null 2>&1 || true; done
sleep_s "$DPMS_KICK_SLEEP"
for n in "${names[@]}"; do hypr dispatch dpms on "$n" >/dev/null 2>&1 || true; done
sleep_s "$DPMS_KICK_SLEEP"
done
}
enforce_triple_order() {
# Keep HDMI between the laptop panel and DP-2, with DP-2 always on the right.
enabled_monitor_names | grep -qx "$MIDDLE_EXTERNAL_NAME" || return 0
enabled_monitor_names | grep -qx "$RIGHT_EXTERNAL_NAME" || return 0
local py=0 px=0 pspan=1920 mspan=1920 mx rx
if jqok; then
read -r px py pspan < <(monjson | jq -r --arg N "$PRIMARY" '
.[] | select(.name == $N) |
(.transform // 0) as $t |
"\(.x // 0) \(.y // 0) \(if (($t % 2) == 1) then (.height // 1080) else (.width // 1920) end)"
')
read -r mspan < <(monjson | jq -r --arg N "$MIDDLE_EXTERNAL_NAME" '
.[] | select(.name == $N) |
(.transform // 0) as $t |
"\(if (($t % 2) == 1) then (.height // 1080) else (.width // 1920) end)"
')
fi
mx=$((px + pspan))
rx=$((mx + mspan))
hypr keyword monitor "$MIDDLE_EXTERNAL_NAME, highres@highrr, ${mx}x${py}, 1, transform, 1" >/dev/null 2>&1 || true
hypr keyword monitor "$RIGHT_EXTERNAL_NAME, highres@highrr, ${rx}x${py}, 1" >/dev/null 2>&1 || true
}
apply_profile() {
local label="$1"
local batch="$2"
dock_settle
[ -n "$batch" ] && hypr_batch "$batch" >/dev/null
dock_settle
kick_externals
notify "Profile: $label" ""
restart_waybar
}
profile_laptop() {
# Minimal: disable only currently enabled externals
local batch=""
batch+="$(batch_disable_enabled_externals)"
apply_profile "Laptop-only" "$batch"
}
profile_dual() {
# Disable enabled externals, then enable preferred main external rule
local batch=""
batch+="$(batch_disable_enabled_externals)"
batch+="$(enable_rule "$DUAL_MAIN")"
apply_profile "Dual" "$batch"
}
profile_triple() {
# Disable enabled externals, then enable both rules
# Order: EXT2 first so it tends to appear “middle” with auto-right
local batch=""
batch+="$(batch_disable_enabled_externals)"
batch+="$(enable_rule "$EXT2")"
batch+="$(enable_rule "$EXT1")"
apply_profile "Triple" "$batch"
dock_settle
enforce_triple_order
}
toggle_externals() {
if external_enabled; then
profile_laptop
else
if [ -n "$EXT1" ] && [ -n "$EXT2" ]; then
profile_triple
else
profile_dual
fi
fi
}
dpms_toggle_focused() {
local name
name="$(focused_monitor_name)"
[ -z "$name" ] && { notify "No focused monitor" ""; exit 2; }
hypr dispatch dpms toggle "$name" >/dev/null
notify "DPMS toggle" "$name"
}
status() {
echo "Enabled monitors:"
enabled_monitor_names | sed 's/^/ - /'
echo
echo "Focused: $(focused_monitor_name || true)"
}
case "${1:-}" in
laptop) profile_laptop ;;
dual) profile_dual ;;
triple) profile_triple ;;
toggle-externals) toggle_externals ;;
dpms-toggle-focused) dpms_toggle_focused ;;
kick-externals) dock_settle; kick_externals; notify "Kicked externals (DPMS)" "" ;;
status) status ;;
*)
cat <<EOF
Usage:
$(basename "$0") laptop # eDP-2 only (disable enabled externals)
$(basename "$0") dual # eDP-2 + preferred external
$(basename "$0") triple # eDP-2 + both externals
$(basename "$0") toggle-externals # laptop-only <-> (dual/triple)
$(basename "$0") dpms-toggle-focused # blank/unblank focused output (layout unchanged)
$(basename "$0") kick-externals # DPMS off/on for enabled externals
$(basename "$0") status # print enabled + focused
EOF
exit 2
;;
esac