Skip to content

Commit 77fa84f

Browse files
balloobclaude
andauthored
Improve PulseAudio/PipeWire integration and audio device discovery (#240)
## Summary This PR enhances Sendspin's audio output handling on Linux desktop environments by properly identifying the application to the sound server and improving the audio device discovery experience. ## Key Changes - **PulseAudio/PipeWire metadata**: Added `_set_pulse_client_metadata()` function that sets libpulse environment variables before client connection, allowing the audio stream to register as "Sendspin" instead of a generic "ALSA plug-in [python3]" name. Users can override these defaults by setting their own environment variables. - **Audio device recommendations**: Enhanced `list_audio_devices()` to display recommended desktop audio routing options (`pulse`, `pipewire`, `default`) when available, making it easier for users to choose appropriate devices. - **Improved help text**: Updated help documentation for the `--audio-device` argument in both player and daemon modes to explicitly mention that `pulse`, `pipewire`, and `default` are available routing options on Linux desktops. - **Automatic initialization**: The metadata setup is called automatically in `main()` before argument parsing, ensuring proper identification with the sound server from the start. ## Implementation Details - The metadata function uses `os.environ.setdefault()` to respect user overrides - Recommended devices are filtered to only show those actually available on the system - The changes are backward compatible and only affect Linux desktop environments using PulseAudio or PipeWire https://claude.ai/code/session_01H1fE1RpT8dQd3FEhveB5qe --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 23a1760 commit 77fa84f

1 file changed

Lines changed: 40 additions & 3 deletions

File tree

sendspin/cli.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,25 @@ def arg_str_to_bool(v: str) -> bool:
6363
raise argparse.ArgumentTypeError("Expected true or false")
6464

6565

66+
def _set_pulse_client_metadata() -> None:
67+
"""Identify the audio stream as Sendspin to the desktop sound server.
68+
69+
libpulse reads these env vars when it opens the client connection
70+
(used by the ALSA ``pulse`` / ``pipewire`` plug devices and by
71+
pipewire-pulse). Without them, the stream registers under a generic
72+
name like ``ALSA plug-in [python3]``. Only set values that aren't
73+
already in the environment so the user can override.
74+
"""
75+
defaults = {
76+
"PULSE_PROP_application.name": "Sendspin",
77+
"PULSE_PROP_application.id": "org.sendspin.cli",
78+
"PULSE_PROP_application.icon_name": "audio-x-generic",
79+
"PULSE_PROP_media.role": "music",
80+
}
81+
for key, value in defaults.items():
82+
os.environ.setdefault(key, value)
83+
84+
6685
def list_audio_devices() -> None:
6786
"""List all available audio output devices."""
6887
try:
@@ -106,6 +125,19 @@ def list_audio_devices() -> None:
106125
if description:
107126
print(f" {description}")
108127

128+
alsa_names = {name for name, _ in alsa_devices}
129+
recommended = [
130+
("pulse", "Routes through PulseAudio"),
131+
("pipewire", "Routes through PipeWire"),
132+
("default", "System default"),
133+
]
134+
recommended = [(n, d) for n, d in recommended if n in alsa_names]
135+
if recommended:
136+
print("\nRecommended for desktop usage:")
137+
print()
138+
for name, description in recommended:
139+
print(f" {name:<12} {description}")
140+
109141

110142
def _add_player_runtime_options(target: ArgumentTarget, *, suppress_defaults: bool = False) -> None:
111143
"""Add the interactive player's runtime options."""
@@ -146,7 +178,8 @@ def _add_player_runtime_options(target: ArgumentTarget, *, suppress_defaults: bo
146178
help=(
147179
"Audio output device by index (e.g., 0, 1, 2), name prefix (e.g., 'MacBook'), "
148180
"or raw ALSA device name (e.g., 'dmixer', 'olohuone') for plugin devices like dmix. "
149-
"Use 'sendspin audio-devices list' to see enumerated devices."
181+
"On Linux desktops, 'pulse', 'pipewire', or 'default' route through the sound "
182+
"server. Use 'sendspin audio-devices list' to see enumerated devices."
150183
),
151184
)
152185
target.add_argument(
@@ -375,8 +408,10 @@ def _build_parser() -> argparse.ArgumentParser:
375408
type=str,
376409
default=None,
377410
help=(
378-
"Audio output device by index (e.g., 0, 1, 2) or name prefix (e.g., 'MacBook'). "
379-
"Use 'sendspin audio-devices list' to see available devices."
411+
"Audio output device by index (e.g., 0, 1, 2), name prefix (e.g., 'MacBook'), "
412+
"or raw ALSA device name (e.g., 'dmixer', 'olohuone') for plugin devices like dmix. "
413+
"On Linux desktops, 'pulse', 'pipewire', or 'default' route through the sound "
414+
"server. Use 'sendspin audio-devices list' to see available devices."
380415
),
381416
)
382417
daemon_parser.add_argument(
@@ -693,6 +728,8 @@ async def _run_daemon_mode(
693728

694729
def main() -> int:
695730
"""Run the CLI client."""
731+
if sys.platform.startswith("linux"):
732+
_set_pulse_client_metadata()
696733
args = parse_args(sys.argv[1:])
697734

698735
# Handle serve subcommand

0 commit comments

Comments
 (0)