From daa15426723623d9c16d42ca4895bb901ac7699c Mon Sep 17 00:00:00 2001 From: Steve Cliff Date: Mon, 13 Apr 2026 09:21:44 +0100 Subject: [PATCH] Add input device selection to interactive config TUI Enumerates available audio input devices via cpal and presents them in a dropdown, with "System default" as the first option. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/cli/config_cmd.rs | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/cli/config_cmd.rs b/src/cli/config_cmd.rs index d96e34e..fa94413 100644 --- a/src/cli/config_cmd.rs +++ b/src/cli/config_cmd.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use cpal::traits::{DeviceTrait, HostTrait}; use dialoguer::{Input, Select}; use std::time::Duration; @@ -105,6 +106,8 @@ pub fn interactive() -> Result<()> { .interact()?; config.audio_feedback = feedback_idx == 0; + config.input_device = prompt_input_device(config.input_device.as_deref())?; + let vad_idx = Select::new() .with_prompt("VAD (voice activity detection)") .items(&["enabled", "disabled"]) @@ -122,6 +125,52 @@ pub fn interactive() -> Result<()> { Ok(()) } +/// Prompt the user to select an audio input device from available devices. +fn prompt_input_device(current: Option<&str>) -> Result> { + let current_label = current.unwrap_or("system default"); + + let host = cpal::default_host(); + let mut device_names: Vec = Vec::new(); + + if let Ok(devices) = host.input_devices() { + for device in devices { + if let Ok(name) = device.name() { + device_names.push(name); + } + } + } + + // Build selection list: "System default" first, then each detected device + let mut items: Vec = vec!["System default".to_string()]; + for name in &device_names { + items.push(name.clone()); + } + + // Find the default selection index + let default_idx = if let Some(cur) = current { + let cur_lower = cur.to_lowercase(); + device_names + .iter() + .position(|n| n.to_lowercase().contains(&cur_lower)) + .map(|i| i + 1) // offset by 1 for "System default" + .unwrap_or(0) + } else { + 0 + }; + + let sel = Select::new() + .with_prompt(format!("Input device (current: {current_label})")) + .items(&items) + .default(default_idx) + .interact()?; + + if sel == 0 { + Ok(None) + } else { + Ok(Some(device_names[sel - 1].clone())) + } +} + /// Prompt the user to either press a key combination or type it manually. fn prompt_hotkey(label: &str, current: &str) -> Result { let choice = Select::new()