fix(voice): honor forwarded audio (PIPEWIRE_REMOTE) in WSL detection

This commit is contained in:
Slippy87 2026-06-02 10:44:19 +00:00 committed by Teknium
parent 2a75664c0c
commit ef686c3878
2 changed files with 68 additions and 8 deletions

View file

@ -0,0 +1,58 @@
"""Regression: WSL voice detection must honor PIPEWIRE_REMOTE, not only PULSE_SERVER.
detect_audio_environment() honors forwarded audio (has_forwarded_audio =
PULSE_SERVER or PIPEWIRE_REMOTE or a reachable socket) in the SSH and container
blocks, but the WSL block previously checked only PULSE_SERVER so a WSL user
with PipeWire forwarding (PIPEWIRE_REMOTE) was wrongly blocked from voice mode.
These tests mock /proc/version so they reproduce the WSL path on any host.
"""
import builtins
import io
from unittest.mock import MagicMock
WSL = "Linux version 5.15.0-microsoft-standard-WSL2 (oe-user@oe-host)"
def _force_wsl(monkeypatch, content=WSL):
real_open = builtins.open
def fake_open(file, *a, **k):
if str(file) == "/proc/version":
return io.StringIO(content)
return real_open(file, *a, **k)
monkeypatch.setattr(builtins, "open", fake_open)
def _base(monkeypatch):
for v in ("SSH_CLIENT", "SSH_TTY", "SSH_CONNECTION", "PULSE_SERVER"):
monkeypatch.delenv(v, raising=False)
monkeypatch.delenv("PIPEWIRE_REMOTE", raising=False)
monkeypatch.setattr("hermes_constants.is_container", lambda: False)
monkeypatch.setattr("tools.voice_mode._pulse_socket_reachable", lambda: False)
sd = MagicMock(); sd.query_devices.return_value = [{"name": "dev"}]
monkeypatch.setattr("tools.voice_mode._import_audio", lambda: (sd, MagicMock()))
def test_wsl_with_pipewire_remote_allows_voice(monkeypatch):
_base(monkeypatch)
monkeypatch.setenv("PIPEWIRE_REMOTE", "/run/user/1000/pipewire-0")
_force_wsl(monkeypatch)
from tools.voice_mode import detect_audio_environment
result = detect_audio_environment()
assert result["available"] is True, result["warnings"]
def test_wsl_with_pulse_server_still_allows_voice(monkeypatch):
_base(monkeypatch)
monkeypatch.setenv("PULSE_SERVER", "unix:/mnt/wslg/PulseServer")
_force_wsl(monkeypatch)
from tools.voice_mode import detect_audio_environment
assert detect_audio_environment()["available"] is True
def test_wsl_without_forwarding_still_blocks(monkeypatch):
_base(monkeypatch)
_force_wsl(monkeypatch) # no PULSE_SERVER, no PIPEWIRE_REMOTE
from tools.voice_mode import detect_audio_environment
res = detect_audio_environment()
assert res["available"] is False
assert any("WSL" in w for w in res["warnings"])

View file

@ -207,19 +207,21 @@ def detect_audio_environment() -> dict:
" PipeWire: -e PIPEWIRE_REMOTE=$XDG_RUNTIME_DIR/pipewire-0"
)
# WSL detection — PulseAudio bridge makes audio work in WSL.
# Only block if PULSE_SERVER is not configured.
# WSL detection — a reachable sound server makes audio work in WSL.
# Honor any forwarding (PulseAudio bridge OR a forwarded PipeWire/Pulse
# socket), mirroring the SSH and container blocks above. Only block when
# no forwarding is configured.
try:
with open('/proc/version', 'r', encoding="utf-8") as f:
if 'microsoft' in f.read().lower():
if os.environ.get('PULSE_SERVER'):
notices.append("Running in WSL with PulseAudio bridge")
if has_forwarded_audio:
notices.append("Running in WSL with a reachable PulseAudio/PipeWire sound server")
else:
warnings.append(
"Running in WSL -- audio requires PulseAudio bridge.\n"
" 1. Set PULSE_SERVER=unix:/mnt/wslg/PulseServer\n"
" 2. Create ~/.asoundrc pointing ALSA at PulseAudio\n"
" 3. Verify with: arecord -d 3 /tmp/test.wav && aplay /tmp/test.wav"
"Running in WSL -- audio requires a forwarded sound server.\n"
" PulseAudio: export PULSE_SERVER=unix:/mnt/wslg/PulseServer\n"
" PipeWire: export PIPEWIRE_REMOTE=$XDG_RUNTIME_DIR/pipewire-0\n"
" Then verify: arecord -d 3 /tmp/test.wav && aplay /tmp/test.wav"
)
except (FileNotFoundError, PermissionError, OSError):
pass