* Resolve effect_input/effect_output audio filter pairs User filter chains can name their input and output nodes differently, so the existing input-name prefix check cannot find their downstream sink. Match effect_output.<suffix> exactly for effect_input.<suffix>, avoiding similarly named filters while retaining prefix-compatible custom tunings and the existing EasyEffects fallbacks. Generated by GPT-6 in Codex. Reviewed by Claude Opus 5.5 Medium. Co-Authored-By: Claude Opus 5.5 Medium <noreply@anthropic.com> * Prefer an exact filter output over legacy prefix matches An earlier stream for a similarly named filter can satisfy the legacy prefix check and select the wrong device before the exact paired output is read. Keep the first prefix candidate as a fallback for effect_input sinks and finish searching for the exact effect_output pair. Preserve first-match behavior for all other sink names. Generated by GPT-6 in Codex. Reviewed by Claude Opus 5.5 Medium. Co-Authored-By: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: Omarchy Bot <317366263+omarchybot@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 Medium <noreply@anthropic.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
97 lines
3.5 KiB
Bash
Executable File
97 lines
3.5 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Print the sink whose volume and mute a given output really uses
|
|
# omarchy:args=[sink-name]
|
|
# omarchy:group=audio
|
|
# omarchy:examples=omarchy audio output sink | omarchy audio output sink omarchy_speaker_tuning
|
|
|
|
set -uo pipefail
|
|
|
|
# A DSP sink -- a speaker tuning filter-chain, or EasyEffects -- can be the
|
|
# selected output without being where loudness lives. Changing its volume alters
|
|
# the level going *into* the processing: the display moves while the speakers do
|
|
# not, and on a chain with a compressor or limiter the tone changes too. Resolve
|
|
# through it to the physical sink it feeds.
|
|
#
|
|
# With no argument this resolves the current default output, so when headphones or
|
|
# HDMI are selected it returns those, not the speakers a tuning happens to front.
|
|
# Callers that need to describe some *other* output -- an output switcher naming
|
|
# the next one in the rotation -- pass that sink explicitly.
|
|
|
|
sink="${1:-$(pactl get-default-sink 2>/dev/null)}"
|
|
|
|
if [[ -z $sink || $sink == alsa_output.* ]]; then
|
|
printf '%s\n' "$sink"
|
|
exit 0
|
|
fi
|
|
|
|
# A DSP sink feeds its physical output through a stream of its own; follow that
|
|
# stream down to the sink underneath.
|
|
downstream="$(pactl list sink-inputs 2>/dev/null |
|
|
awk -v virt="$sink" '
|
|
BEGIN {
|
|
output = virt
|
|
sub(/^effect_input\./, "effect_output.", output)
|
|
}
|
|
/^Sink Input #/ {target = ""}
|
|
/^[[:space:]]*Sink:/ {target = $2}
|
|
/node\.name = / {
|
|
name = $0
|
|
sub(/.*node\.name = "/, "", name)
|
|
sub(/"$/, "", name)
|
|
if (target != "" && (name == output || (output == virt && index(name, virt) == 1))) {selected = target; exit}
|
|
if (target != "" && fallback == "" && index(name, virt) == 1) fallback = target
|
|
}
|
|
/application\.name = "EasyEffects"/ {
|
|
if (virt == "easyeffects_sink" && target != "") {selected = target; exit}
|
|
}
|
|
END {
|
|
if (selected != "") print selected
|
|
else if (fallback != "") print fallback
|
|
}')"
|
|
|
|
if [[ -n $downstream ]]; then
|
|
name="$(pactl list sinks short 2>/dev/null |
|
|
awk -v id="$downstream" '$1 == id {print $2; exit}')"
|
|
if [[ -n $name ]]; then
|
|
printf '%s\n' "$name"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# EasyEffects 8 does not play into the physical sink through a stream of its
|
|
# own: its output node links its ports straight to the sink's playback ports,
|
|
# so the sink-input search above finds nothing and the keys would move the
|
|
# EasyEffects sink -- whose volume its monitor, the input of the processing,
|
|
# does not follow. Follow the port links instead: the first ALSA or Bluetooth
|
|
# sink any EasyEffects output port is linked to.
|
|
if [[ $sink == "easyeffects_sink" ]]; then
|
|
linked="$(pw-link -l 2>/dev/null |
|
|
awk '
|
|
/^[^ ]/ {src = $0}
|
|
/^[[:space:]]*\|->/ {
|
|
if (src ~ /^ee_/ && $2 ~ /^(alsa|bluez)_output\..*:playback_/) {
|
|
sub(/:playback_.*$/, "", $2); print $2; exit
|
|
}
|
|
}')"
|
|
|
|
# Idle, EasyEffects drops those links: fall back to the output device it is
|
|
# configured to feed, when that sink exists.
|
|
if [[ -z $linked ]]; then
|
|
configured="$(sed -n 's/^outputDevice=//p' \
|
|
"${XDG_CONFIG_HOME:-$HOME/.config}/easyeffects/db/easyeffectsrc" 2>/dev/null)"
|
|
if [[ -n $configured ]] && pactl list sinks short 2>/dev/null | awk '{print $2}' | grep -qxF -- "$configured"; then
|
|
linked="$configured"
|
|
fi
|
|
fi
|
|
|
|
if [[ -n $linked ]]; then
|
|
printf '%s\n' "$linked"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# Nothing resolvable downstream -- the DSP sink may simply be idle and unlinked.
|
|
# Fall back to the sink itself so callers still have something to act on.
|
|
printf '%s\n' "$sink"
|