diff --git a/README.md b/README.md index d2cbcf3..4c09c48 100644 --- a/README.md +++ b/README.md @@ -171,17 +171,24 @@ Audio output devices (* = selected by the current config): ... ``` -Then pin one in `crabidy-server.toml` — the value is matched +Then pin one by passing it to the same command — the value is matched case-insensitively as a substring of the name, so a memorable fragment is enough — and restart the server: +```console +$ crabidy-server audio-devices Headphones +Set [audio] device = "Headphones" in .../crabidy-server.toml +``` + +That writes `[audio] device` for you; you can also edit it by hand: + ```toml [audio] device = "Headphones" ``` -If the name matches nothing, the server logs a warning and falls back to -the system default. +If the name matches nothing, both the command and the server warn, and the +server falls back to the system default. ## Command line diff --git a/cbd-cli/src/lib.rs b/cbd-cli/src/lib.rs index 9bdb510..251909d 100644 --- a/cbd-cli/src/lib.rs +++ b/cbd-cli/src/lib.rs @@ -179,6 +179,15 @@ pub struct CompletionsArgs { pub shell: clap_complete::Shell, } +/// `audio-devices [device]`: list the output devices, or — with `device` — set +/// `[audio] device` in `crabidy-server.toml`. +#[derive(Debug, Args)] +pub struct AudioDevicesArgs { + /// A name (or case-insensitive fragment) of an output device to write into + /// `[audio] device`. Omit to just list the available devices. + pub device: Option, +} + /// Subcommands of `crabidy-server`. #[derive(Debug, Subcommand)] pub enum ServerCommand { @@ -195,8 +204,8 @@ pub enum ServerCommand { /// Playback / global operations against a running server. #[command(subcommand)] Global(GlobalCmd), - /// List the audio output devices (to pick `[audio] device`). - AudioDevices, + /// List audio output devices, or set `[audio] device` if one is given. + AudioDevices(AudioDevicesArgs), /// Print a shell completion script. Completions(CompletionsArgs), } @@ -251,8 +260,8 @@ pub enum CbdCommand { Queue(QueueCmd), #[command(subcommand)] Global(GlobalCmd), - /// List the audio output devices (to pick `[audio] device`). - AudioDevices, + /// List audio output devices, or set `[audio] device` if one is given. + AudioDevices(AudioDevicesArgs), /// Print a shell completion script. Completions(CompletionsArgs), } diff --git a/cbd/src/main.rs b/cbd/src/main.rs index f3ef302..de813eb 100644 --- a/cbd/src/main.rs +++ b/cbd/src/main.rs @@ -92,7 +92,7 @@ async fn run_command( match command { CbdCommand::Guard(args) => server_cli::guard(args).await, CbdCommand::Scan(args) => server_cli::scan(args).await, - CbdCommand::AudioDevices => server_cli::audio_devices(), + CbdCommand::AudioDevices(args) => server_cli::audio_devices(args.device), CbdCommand::Auth(args) => { let password = args .password diff --git a/crabidy-server/src/cli.rs b/crabidy-server/src/cli.rs index 59ca454..db58580 100644 --- a/crabidy-server/src/cli.rs +++ b/crabidy-server/src/cli.rs @@ -102,12 +102,43 @@ pub async fn guard(args: GuardArgs) -> Result<(), Box> { Ok(()) } -/// `audio-devices`: list the available audio output devices so the user can -/// choose one for `[audio] device` in `crabidy-server.toml`. Marks the device -/// the current config selects, using the same case-insensitive substring match -/// the server applies at startup. Fixes the common Raspberry Pi case where the -/// default device is HDMI and audio plays but is silent on the jack/DAC. -pub fn audio_devices() -> Result<(), Box> { +/// `audio-devices [device]`: with no argument, list the available audio output +/// devices so the user can choose one for `[audio] device` in +/// `crabidy-server.toml`, marking the device the current config selects (same +/// case-insensitive substring match the server applies at startup). With a +/// `device` argument, write it into `[audio] device` and then list — so one +/// command both configures and confirms. Fixes the common Raspberry Pi case +/// where the default device is HDMI and audio plays but is silent on the +/// jack/DAC. +pub fn audio_devices(select: Option) -> Result<(), Box> { + // Setting a device needs a writable config dir; listing tolerates its + // absence. + if let Some(device) = select { + let dir = config_dir()?; + let mut settings = ServerSettings::load(&dir)?; + settings.audio.device = Some(device.clone()); + settings.store(&dir)?; + println!( + "Set [audio] device = \"{device}\" in {}", + dir.join(crate::settings::SETTINGS_FILE).display() + ); + // The server matches the same way; warn on a fragment that currently + // resolves to nothing so a typo is caught here, not as silent output. + let needle = device.to_lowercase(); + let names = audio_player::output_device_names(); + if !names.is_empty() + && !names + .iter() + .any(|name| name.to_lowercase().contains(&needle)) + { + eprintln!( + "warning: no current output device name contains \"{device}\"; the server \ + will fall back to the system default until one matches" + ); + } + println!("Restart the server for it to take effect.\n"); + } + let dir = config_dir().ok(); let configured = dir .as_deref() diff --git a/crabidy-server/src/main.rs b/crabidy-server/src/main.rs index d49574d..283a85f 100644 --- a/crabidy-server/src/main.rs +++ b/crabidy-server/src/main.rs @@ -41,7 +41,7 @@ async fn run_command( match command { ServerCommand::Guard(args) => cli::guard(args).await, ServerCommand::Scan(args) => cli::scan(args).await, - ServerCommand::AudioDevices => cli::audio_devices(), + ServerCommand::AudioDevices(args) => cli::audio_devices(args.device), ServerCommand::Library(cmd) => { cbd_cli::run_remote(&cli::connection(remote), RemoteCmd::Library(cmd)).await }