diff --git a/default/audio/filter-chain-host.conf b/default/audio/filter-chain-host.conf index 4951baea..01a35c1e 100644 --- a/default/audio/filter-chain-host.conf +++ b/default/audio/filter-chain-host.conf @@ -23,9 +23,12 @@ context.spa-libs = { } context.modules = [ - # Boost the audio thread priority. + # Boost the audio thread priority. Ask RTKit directly, as pipewire.conf + # does: a portal started before rtkit existed reports a realtime budget of + # zero, which becomes a hard RLIMIT_RTTIME of 0 while RTKit still makes the + # data thread realtime, and the kernel then kills the host once audio plays. { name = libpipewire-module-rt - args = { } + args = { rtportal.enabled = false } flags = [ ifexists nofail ] } diff --git a/migrations/1790114905.sh b/migrations/1790114905.sh new file mode 100644 index 00000000..293240e8 --- /dev/null +++ b/migrations/1790114905.sh @@ -0,0 +1,22 @@ +echo "Ask RTKit directly for the speaker tuning's realtime priority" + +# The tuning host asked for realtime through xdg-desktop-portal. A portal started +# before rtkit was installed reports a realtime budget of zero; module-rt applies +# it as a hard RLIMIT_RTTIME of 0, RTKit still makes the data thread realtime, and +# the kernel kills the host as soon as audio plays. The host config is only copied +# by `omarchy-audio-tuning on`, so an installed tuning keeps the old one until +# replaced here. +# +# Only Omarchy's own copy with the old setting is replaced. A community +# calibrator can host its graph under the same file name, and a tuning graph is +# never touched: `omarchy-audio-tuning on` would overwrite one it did not write. +host_config="${XDG_CONFIG_HOME:-$HOME/.config}/pipewire/omarchy-speaker-tuning.conf" +host_source="$OMARCHY_PATH/default/audio/filter-chain-host.conf" + +if [[ -f $host_config && ! -L $host_config ]] && + grep -qx '# Host config for the Omarchy speaker tuning.' "$host_config" && + grep -q 'args = { }' "$host_config"; then + install -Dm644 "$host_source" "$host_config" + # A restart ends playback for a moment, so only a running host is restarted. + systemctl --user try-restart omarchy-speaker-tuning.service >/dev/null 2>&1 || true +fi diff --git a/test/shell.d/speaker-tuning-rtkit-migration-test.sh b/test/shell.d/speaker-tuning-rtkit-migration-test.sh new file mode 100644 index 00000000..3a876222 --- /dev/null +++ b/test/shell.d/speaker-tuning-rtkit-migration-test.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +set -euo pipefail +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT +mkdir -p "$tmp_dir/bin" +export XDG_CONFIG_HOME="$tmp_dir/config" OMARCHY_PATH="$ROOT" CALL_LOG="$tmp_dir/calls" +export PATH="$tmp_dir/bin:$PATH" + +cat > "$tmp_dir/bin/systemctl" <<'SH' +#!/bin/bash +printf '%s\n' "$*" >> "$CALL_LOG" +SH +chmod +x "$tmp_dir/bin/systemctl" + +migration="$ROOT/migrations/1790114905.sh" +host_source="$ROOT/default/audio/filter-chain-host.conf" +host_config="$XDG_CONFIG_HOME/pipewire/omarchy-speaker-tuning.conf" +mkdir -p "${host_config%/*}" + +grep -q 'args = { rtportal.enabled = false }' "$host_source" || + fail "the shipped tuning host asks RTKit directly" +pass "the shipped tuning host asks RTKit directly" + +# The copy an earlier Omarchy installed: the same file with the old setting. +sed 's/args = { rtportal.enabled = false }/args = { }/' "$host_source" > "$host_config" +: > "$CALL_LOG" +bash -euo pipefail "$migration" >/dev/null +cmp -s "$host_source" "$host_config" || fail "an installed Omarchy host is replaced" +grep -Fxq -- "--user try-restart omarchy-speaker-tuning.service" "$CALL_LOG" || + fail "a replaced host is restarted if it runs" +pass "an installed Omarchy host is replaced and restarted" + +: > "$CALL_LOG" +bash -euo pipefail "$migration" >/dev/null +[[ ! -s $CALL_LOG ]] || fail "the migration is idempotent" +pass "the migration is idempotent" + +# Another tool's host under the same name, as a community calibrator writes it. +foreign='context.properties = { log.level = 0 } +context.modules = [ + { name = libpipewire-module-rt args = { } flags = [ ifexists nofail ] } +]' +printf '%s\n' "$foreign" > "$host_config" +: > "$CALL_LOG" +bash -euo pipefail "$migration" >/dev/null +[[ $(cat "$host_config") == "$foreign" && ! -s $CALL_LOG ]] || + fail "a host another tool wrote is left alone" +pass "a host another tool wrote is left alone" + +rm -f "$host_config" +bash -euo pipefail "$migration" >/dev/null +[[ ! -e $host_config && ! -s $CALL_LOG ]] || fail "no tuning installed means nothing to do" +pass "no tuning installed means nothing to do"