-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
272 lines (255 loc) · 11.5 KB
/
Copy pathinstall.sh
File metadata and controls
272 lines (255 loc) · 11.5 KB
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026 Nakildias <nakildiaspro@gmail.com>
# Elgato Wave:3 (0fd9:0070) -- installer for Arch, Fedora and Debian/Ubuntu.
# https://github.com/Nakildias/elgato-wave3-linux
#
# sudo ./install.sh
#
# Three independent parts:
#
# 1. kernel patch fixes the usb_set_interface -110 lockup (DKMS build)
# 2. wireplumber keeps the capture node open, so the mic is not silent
# whenever playback is active
# 3. pipewire raises the graph quantum so the Pro Audio sink stops
# starving (stuttering)
#
# Parts 2 and 3 are pure config and work on any kernel. Part 1 needs kernel
# headers matching the *running* kernel; if that is not possible the script
# says so and still installs 2 and 3, because they fix the audible problems.
#
# Not supported: atomic/image-based distros (Silverblue, Kinoite, Bazzite,
# SteamOS, openSUSE MicroOS, ...). DKMS there needs rpm-ostree layering or a
# systemd sysext, and this script would not do the right thing.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# NB: do NOT call these NAME/VERSION -- /etc/os-release defines those
# and sourcing it below would silently clobber them (NAME="Arch Linux").
DKMS_NAME="snd-usb-audio-wave3"; DKMS_VER="1.0"
KREL="$(uname -r)"
FAILED_KERNEL=0
NEED_REBOOT=0
say() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
ok() { printf ' \033[32mok\033[0m %s\n' "$*"; }
warn() { printf ' \033[33mwarn\033[0m %s\n' "$*"; }
die() { printf '\n\033[31mxx %s\033[0m\n' "$*" >&2; exit 1; }
# --------------------------------------------------------------- preflight
[[ $EUID -eq 0 ]] || die "run as root: sudo ./install.sh"
RUSER="${SUDO_USER:-}"
[[ -n "$RUSER" && "$RUSER" != "root" ]] \
|| die "run via sudo from your normal user account (sudo ./install.sh), not as root"
UID_N="$(id -u "$RUSER")"; UD="/run/user/$UID_N"
HOME_N="$(getent passwd "$RUSER" | cut -d: -f6)"
[[ -d "$HOME_N" ]] || die "cannot resolve home directory for $RUSER"
runu() { runuser -u "$RUSER" -- env XDG_RUNTIME_DIR="$UD" \
DBUS_SESSION_BUS_ADDRESS="unix:path=$UD/bus" "$@"; }
# Atomic / image-based systems: refuse rather than half-work.
if [[ -f /run/ostree-booted ]] || [[ -d /ostree ]] \
|| command -v rpm-ostree >/dev/null 2>&1 \
|| command -v transactional-update >/dev/null 2>&1; then
die "atomic/image-based distro detected -- not supported.
DKMS there needs rpm-ostree layering or a systemd sysext.
The userspace fixes still work; copy userspace/51-wave3.conf to
~/.config/wireplumber/wireplumber.conf.d/ and
userspace/51-wave3-quantum.conf to ~/.config/pipewire/pipewire.conf.d/,
then: systemctl --user restart wireplumber"
fi
if ! touch /usr/.wave3-writetest 2>/dev/null; then
die "/usr is read-only -- immutable system. Not supported (see above)."
fi
rm -f /usr/.wave3-writetest
kbuild() {
[[ -d "/usr/lib/modules/$KREL/build" ]] && { echo "/usr/lib/modules/$KREL/build"; return; }
[[ -d "/lib/modules/$KREL/build" ]] && { echo "/lib/modules/$KREL/build"; return; }
}
# ------------------------------------------------------------ distro + pkgs
# Read os-release in subshells so it cannot clobber our variables.
OS_ID="$(. /etc/os-release 2>/dev/null; echo "${ID:-}")"
OS_LIKE="$(. /etc/os-release 2>/dev/null; echo "${ID_LIKE:-}")"
OS_PRETTY="$(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-}")"
case "$OS_ID $OS_LIKE" in
*arch*) FAM=arch ;;
*fedora*|*rhel*) FAM=fedora ;;
*debian*|*ubuntu*) FAM=debian ;;
*) FAM=unknown ;;
esac
say "System"
ok "distro: ${OS_PRETTY:-${OS_ID:-unknown}} (family: $FAM)"
ok "kernel: $KREL"
HAD_HEADERS=0; [[ -n "$(kbuild)" ]] && HAD_HEADERS=1
say "Installing build dependencies"
case "$FAM" in
arch)
PKGBASE="$(cat "/usr/lib/modules/$KREL/pkgbase" 2>/dev/null || echo linux)"
PKGS=(dkms "${PKGBASE}-headers" base-devel curl python tar xz usbutils)
ok "pacman -S ${PKGS[*]}"
# Deliberately NO -y: "pacman -Sy <pkg>" is a partial upgrade and can break
# an Arch system. If the local DB is too old to find a package, tell the
# user to run a full -Syu themselves rather than doing it for them.
if ! pacman -S --needed --noconfirm "${PKGS[@]}"; then
warn "pacman could not install everything."
warn " If a package was 'not found', your package DB is stale --"
warn " run: sudo pacman -Syu then re-run this script."
fi
;;
debian)
export DEBIAN_FRONTEND=noninteractive
PKGS=(dkms "linux-headers-$KREL" build-essential curl python3 xz-utils usbutils)
ok "apt-get install ${PKGS[*]}"
apt-get update -qq || warn "apt-get update failed"
apt-get install -y --no-install-recommends "${PKGS[@]}" || {
warn "no exact headers package for $KREL; trying the generic metapackage"
apt-get install -y --no-install-recommends dkms build-essential curl \
python3 xz-utils usbutils linux-headers-generic || warn "apt install failed"
}
;;
fedora)
PKGS=(dkms "kernel-devel-$KREL" gcc make curl python3 xz tar usbutils elfutils-libelf-devel)
ok "dnf install ${PKGS[*]}"
dnf install -y "${PKGS[@]}" || {
warn "no exact kernel-devel for $KREL; trying the unversioned package"
dnf install -y dkms kernel-devel gcc make curl python3 xz tar usbutils \
elfutils-libelf-devel || warn "dnf install failed"
}
;;
*)
warn "unrecognised distro -- skipping package installation"
warn " needed: dkms, kernel headers for $KREL, gcc, make, curl, python3, tar, xz"
;;
esac
# Headers that do not match the running kernel mean the kernel was updated:
# usable only after a reboot.
if [[ -z "$(kbuild)" ]]; then
OTHER="$(ls -1d /usr/lib/modules/*/build /lib/modules/*/build 2>/dev/null | head -1)"
if [[ -n "$OTHER" ]]; then
warn "headers installed, but not for the running kernel ($KREL)"
warn " found: $OTHER"
[[ $HAD_HEADERS -eq 0 ]] && NEED_REBOOT=1
else
warn "no kernel headers available for $KREL"
fi
else
ok "kernel headers present for $KREL"
fi
# -------------------------------------------------------------- the device
say "Device"
lsusb -d 0fd9:0070 >/dev/null 2>&1 && ok "Elgato Wave:3 detected" \
|| warn "Wave:3 not detected right now -- installing anyway"
CARD=""
for f in /proc/asound/card*/usbid; do
[[ -r "$f" ]] && [[ "$(cat "$f")" == "0fd9:0070" ]] && CARD="$(basename "$(dirname "$f")")"
done
[[ -n "$CARD" ]] && ok "ALSA card: $CARD" || warn "Wave:3 ALSA card not found"
# ------------------------------------------------------------- 1. kernel
say "1/3 Kernel patch (snd-usb-audio)"
KB="$(kbuild)"
if [[ $NEED_REBOOT -eq 1 ]]; then
warn "skipped: reboot required first (see the end of this run)"
FAILED_KERNEL=1
elif ! command -v dkms >/dev/null || [[ -z "$KB" ]]; then
warn "skipped: needs dkms and kernel headers for $KREL"
FAILED_KERNEL=1
else
if "$ROOT/scripts/prepare-src.sh" >/tmp/wave3-prepare.log 2>&1; then
ok "kernel sources staged and patched"
if make -C "$KB" M="$ROOT/src" modules >/tmp/wave3-build.log 2>&1; then
ok "module built"
dkms remove -m "$DKMS_NAME" -v "$DKMS_VER" --all >/dev/null 2>&1
DEST="/usr/src/$DKMS_NAME-$DKMS_VER"; rm -rf "$DEST"; mkdir -p "$DEST"
cp -a "$ROOT/src"/. "$DEST"/; cp -a "$ROOT/dkms/dkms.conf" "$DEST"/
if dkms add -m "$DKMS_NAME" -v "$DKMS_VER" >/dev/null 2>&1 &&
dkms build -m "$DKMS_NAME" -v "$DKMS_VER" -k "$KREL" >/dev/null 2>&1 &&
dkms install -m "$DKMS_NAME" -v "$DKMS_VER" -k "$KREL" --force >/dev/null 2>&1; then
ok "installed via dkms"
else
warn "dkms install failed"; FAILED_KERNEL=1
fi
else
warn "build failed -- see /tmp/wave3-build.log"
warn " the patch anchors may not match kernel $KREL"
FAILED_KERNEL=1
fi
else
warn "could not stage/patch sources -- see /tmp/wave3-prepare.log"
FAILED_KERNEL=1
fi
fi
# ---------------------------------------------------------- 2+3. userspace
say "2/3 WirePlumber rule (microphone)"
WP="$HOME_N/.config/wireplumber/wireplumber.conf.d"
install -d -o "$RUSER" -g "$RUSER" "$WP"
install -m644 -o "$RUSER" -g "$RUSER" "$ROOT/userspace/51-wave3.conf" "$WP/"
ok "~/.config/wireplumber/wireplumber.conf.d/51-wave3.conf"
say "3/3 PipeWire quantum (Pro Audio stutter)"
PW="$HOME_N/.config/pipewire/pipewire.conf.d"
install -d -o "$RUSER" -g "$RUSER" "$PW"
install -m644 -o "$RUSER" -g "$RUSER" "$ROOT/userspace/51-wave3-quantum.conf" "$PW/"
ok "~/.config/pipewire/pipewire.conf.d/51-wave3-quantum.conf"
warn "this adds an ~85ms latency floor to ALL audio devices. If you want low"
warn " latency back, delete that file and use the device's"
warn " 'Analog Stereo Output + Mono Input' profile, which does not need it."
# ------------------------------------------------------------ apply + check
say "Restarting audio stack"
runu systemctl --user stop wireplumber pipewire-pulse.service pipewire-pulse.socket \
pipewire.service pipewire.socket >/dev/null 2>&1
sleep 2
if [[ $FAILED_KERNEL -eq 0 ]]; then
modprobe -r snd_usb_audio 2>/dev/null && modprobe snd-usb-audio 2>/dev/null
fi
runu systemctl --user start pipewire.socket pipewire-pulse.socket >/dev/null 2>&1
runu systemctl --user start wireplumber >/dev/null 2>&1
sleep 6
ok "restarted"
say "Verifying"
case "$(modinfo -F filename snd-usb-audio 2>/dev/null)" in
*updates/dkms*|*extra*) ok "patched module loaded" ;;
*) if [[ $FAILED_KERNEL -eq 1 ]]; then warn "stock module (kernel patch not installed)"
else warn "stock module still loaded -- reboot to pick up the patch"; fi ;;
esac
if [[ -n "$CARD" ]]; then
CST="$(grep -h '^state' "/proc/asound/$CARD/pcm0c/sub0/status" 2>/dev/null || echo closed)"
[[ "$CST" == *RUNNING* ]] && ok "capture stream held open -- this keeps the mic alive" \
|| warn "capture not held open yet ($CST)"
fi
SRC_ID="$(runu pactl list sources short 2>/dev/null | grep -i wave | grep -v monitor | awk '{print $1}' | head -1)"
if [[ -n "${SRC_ID:-}" ]] && command -v pw-record >/dev/null 2>&1; then
T="$(mktemp -u /tmp/wave3-verify.XXXX.wav)"
# timeout must run INSIDE runuser: runu is a shell function, timeout execs binaries.
runu timeout 8 pw-record --target "$SRC_ID" "$T" >/dev/null 2>&1
PEAK="$(python3 - "$T" 2>/dev/null <<'EOF'
import sys,struct,os
p=sys.argv[1]
if not os.path.exists(p) or os.path.getsize(p)<=44: print(-1); raise SystemExit
d=open(p,'rb').read(); i=12; fmt=data=None
while i+8<=len(d):
c=d[i:i+4]; sz=struct.unpack('<I',d[i+4:i+8])[0]
if c==b'fmt ': fmt=d[i+8:i+8+sz]
elif c==b'data': data=d[i+8:i+8+sz]; break
i+=8+sz+(sz&1)
if not data or not fmt: print(-1); raise SystemExit
bits=struct.unpack('<HHIIHH',fmt[:16])[5]; bl=bits//8
print(max((abs(struct.unpack('<h',data[o:o+bl])[0])<<8 if bits==16
else abs(int.from_bytes(data[o:o+bl],'little',signed=True)))
for o in range(0,len(data)-bl+1,bl)))
EOF
)"; rm -f "$T"
if [[ "${PEAK:-0}" -gt 2000 ]]; then ok "microphone produces audio (peak $PEAK)"
else
warn "microphone captured silence (peak ${PEAK:-?})"
warn " if this device was wedged by an earlier lockup, UNPLUG it for 30s"
warn " and re-run -- a reboot does NOT clear it, VBUS stays powered."
fi
else
warn "could not test the microphone (no pw-record, or no Wave:3 source)"
fi
say "Done"
if [[ $NEED_REBOOT -eq 1 ]]; then
printf '\n\033[1;33m REBOOT REQUIRED\033[0m\n'
echo " Kernel headers were just installed but do not match the running"
echo " kernel ($KREL). Reboot, then re-run: sudo ./install.sh"
echo " The microphone and stutter fixes are already active."
elif [[ $FAILED_KERNEL -eq 1 ]]; then
warn "kernel patch not installed; the mic and stutter fixes ARE active."
warn " the kernel part only fixes the -110 lockup."
fi
echo " Revert with: sudo ./uninstall.sh"