blob: f32ded0eb5e0c748290732166ee5787821f5bc3d (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
#!/usr/bin/env bash
# ==================================================
# KoolDots (2026)
# Project URL: https://github.com/LinuxBeginnings
# License: GNU GPLv3
# SPDX-License-Identifier: GPL-3.0-or-later
# ==================================================
set -euo pipefail
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
swayIconDir="${XDG_CONFIG_HOME}/swaync/icons"
#// Credits to sl1ng for the orginal script. Rewritten by Vyle.
ctlcheck=("pactl" "jq" "notify-send" "awk" "pgrep" "hyprctl" "iconv")
missing=()
for ctl in "${ctlcheck[@]}"; do
command -v "${ctl}" >/dev/null || missing+=("${ctl}")
done
if (( ${#missing[@]} )) 2>/dev/null; then
if printf '%s\n' "${missing[@]}" | grep -qx "pactl"; then
notify-send -a "t1" -r 91190 -t 2000 -i "${swayIconDir}/volume-low.png" "ERROR: pactl not installed" "Install 'pactl' (pulseaudio-utils or pipewire-pulse)."
fi
echo "Missing required dependencies: \"${missing[*]}\""
exit 1
fi
#// Parse .pid, .class, .title to __pid, __class, __title.
active_json="$(hyprctl -j activewindow 2>/dev/null || { echo -e "Did hyprctl fail to run? [EXIT-CODE:-1]"; exit 1; } )"
PID="$(jq -r '"\(.pid)\t\(.class)\t\(.title)"' <<< "${active_json}" || { echo -e "Did jq fail to run? [EXIT-CODE:-1]"; exit 1; } )"
IFS=$'\t' read -r __pid __class __title <<< "${PID}"
[[ -z "${__pid}" ]] && { echo -e "Could not resolve PID for focused window."; exit 1; }
sink_json="$(pactl -f json list sink-inputs 2>/dev/null | iconv -f utf-8 -t utf-8 -c || { echo -e "Did pactl or iconv fail to run? Required manual intervention."; exit 1; } )"
#// Collect all descendant PIDs for the active window (Chrome/Wayland audio often runs in child processes).
declare -A seen_pids=()
queue=("${__pid}")
all_pids=()
while ((${#queue[@]})); do
pid="${queue[0]}"
queue=("${queue[@]:1}")
[[ -n "${seen_pids[$pid]:-}" ]] && continue
seen_pids["$pid"]=1
all_pids+=("$pid")
mapfile -t children < <(pgrep -P "$pid" || true)
for child in "${children[@]}"; do
[[ -n "${seen_pids[$child]:-}" ]] || queue+=("$child")
done
done
pidsJson="$(printf '%s\n' "${all_pids[@]}" | jq -s 'map(tonumber)')"
#// Check if any descendant PID matches application.process.id or else verify other statements.
mapfile -t sink_ids < <(jq -r --argjson pids "${pidsJson}" --arg class "${__class}" --arg title "${__title}" '
.[] |
def lc(x): (x // "" | ascii_downcase);
def normalize(x): x | gsub("[-_~.]+";" ") ;
select(
(.properties["application.process.id"] | tostring | tonumber? as $p | $p != null and ($pids | index($p)))
or
(lc(.properties["application.name"]) | contains(lc($class)))
or
(lc(.properties["application.id"]) | contains(lc($class)))
or
(lc(.properties["application.process.binary"]) | contains(lc($class)))
or
(normalize(lc(.properties["media.name"])) | contains(normalize(lc($title))))
) | .index' <<< "${sink_json}"
)
if [[ "${#sink_ids[@]}" -eq 0 ]]; then
mapfile -t fallback_pids < <(pgrep -x "${__class}" || true)
if [[ "${#fallback_pids[@]}" -gt 0 ]]; then
declare -A seen_fallback=()
queue=("${fallback_pids[@]}")
all_fallback=()
while ((${#queue[@]})); do
pid="${queue[0]}"
queue=("${queue[@]:1}")
[[ -n "${seen_fallback[$pid]:-}" ]] && continue
seen_fallback["$pid"]=1
all_fallback+=("$pid")
mapfile -t children < <(pgrep -P "$pid" || true)
for child in "${children[@]}"; do
[[ -n "${seen_fallback[$child]:-}" ]] || queue+=("$child")
done
done
fallbackJson="$(printf '%s\n' "${all_fallback[@]}" | jq -s 'map(tonumber)')"
mapfile -t sink_ids < <( jq -r --argjson pids "${fallbackJson}" '.[] |
select((.properties["application.process.id"] | tostring | tonumber? as $p | $p != null and ($pids | index($p)))) | .index' <<< "${sink_json}" )
fi
fi
#// Auto-Detect if the environment is on Hyprland or $HYPRLAND_INSTANCE_SIGNATURE.
if [[ ${#sink_ids[@]} -eq 0 ]]; then
if [[ -n "${HYPRLAND_INSTANCE_SIGNATURE}" ]]; then
# Even if the fallback_pid remains empty, we will dispatch exit code based on $HYPRLAND_INSTANCE_SIGNATURE.
notify-send -a "t1" -r 91190 -t 1200 -i "${swayIconDir}/volume-low.png" "No sink input for the active_window: ${__class}"
echo "No sink input for focused window: ${__class}"
exit 1
else
echo "No sink input for focused active_window ${__class}"
exit 1
fi
fi
idsJson=$(printf '%s\n' "${sink_ids[@]}" | jq -s 'map(tonumber)')
#// Get the available option from pactl.
want_mute=$(jq -r --argjson ids "$idsJson" '
[ .[] | select(.index as $i | $ids | index($i)) | .mute ] as $m |
if all($m[]; . == true) then "no"
else "yes"
end' <<< "${sink_json}"
)
if [[ "${want_mute}" == "no" ]]; then
state_msg="Unmuted"
swayIcon="${swayIconDir}/volume-high.png"
else
state_msg="Muted"
swayIcon="${swayIconDir}/volume-mute.png"
fi
[[ -f "${swayIcon}" ]] || echo -e "Missing swaync icons."
changed=0
failed_ids=()
for id in "${sink_ids[@]}"; do
if pactl set-sink-input-mute "$id" "$want_mute"; then
changed=1
else
failed_ids+=("$id")
fi
done
if [[ "$changed" -eq 0 ]]; then
notify-send -a "t2" -r 91190 -t 1200 -i "${swayIconDir}/volume-low.png" "Failed to change sink input(s)" "${failed_ids[*]:-unknown}"
exit 1
fi
#// Append pamixer to get a nice result. Pamixer is complete optional here.
if command -v pamixer >/dev/null; then
sink_name="$(pamixer --get-default-sink 2>/dev/null | awk -F '"' 'END{print $(NF - 1)}' 2>/dev/null || true)"
if [[ -n "${sink_name}" ]]; then
notify-send -a "t2" -r 91190 -t 800 -i "${swayIcon}" "${state_msg} ${__class}" "${sink_name}"
else
notify-send -a "t2" -r 91190 -t 800 -i "${swayIcon}" "${state_msg} ${__class}"
fi
else
notify-send -a "t2" -r 91190 -t 800 -i "${swayIcon}" "${state_msg} ${__class}"
fi
|