From 84a2aef83c36a9d877ae4a4a8b564a0a11f52be0 Mon Sep 17 00:00:00 2001 From: Max Schmieder Date: Sun, 10 May 2026 16:55:19 -0400 Subject: [PATCH 1/5] Add microphone device dropdown --- src-tauri/src/audio.rs | 29 +++++++++++++++++++ src-tauri/src/lib.rs | 6 ++++ src/index.html | 5 +++- src/main.js | 63 +++++++++++++++++++++++++++++++++++------- 4 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src-tauri/src/audio.rs b/src-tauri/src/audio.rs index 8fa0b16..f09a15a 100644 --- a/src-tauri/src/audio.rs +++ b/src-tauri/src/audio.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Result}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use crossbeam_channel::{bounded, Receiver, Sender}; +use serde::Serialize; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -16,6 +17,13 @@ pub struct Frame(pub Vec); #[derive(Clone, Copy, Debug)] pub struct Level(pub f32); +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InputDeviceInfo { + pub name: String, + pub is_default: bool, +} + pub struct AudioCapture { stop: Arc, pub frames: Receiver, @@ -75,6 +83,27 @@ impl AudioCapture { } } +pub fn input_devices() -> Result> { + let host = cpal::default_host(); + let default_name = host.default_input_device().and_then(|d| d.name().ok()); + let mut devices = Vec::new(); + + for device in host.input_devices()? { + let Ok(name) = device.name() else { + continue; + }; + let is_default = default_name.as_ref() == Some(&name); + devices.push(InputDeviceInfo { name, is_default }); + } + + devices.sort_by(|a, b| match (a.is_default, b.is_default) { + (true, false) => std::cmp::Ordering::Less, + (false, true) => std::cmp::Ordering::Greater, + _ => a.name.to_lowercase().cmp(&b.name.to_lowercase()), + }); + Ok(devices) +} + fn run_stream( device: cpal::Device, config: cpal::SupportedStreamConfig, diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index dc09400..ffa0d24 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -69,6 +69,11 @@ async fn get_config(state: tauri::State<'_, Arc>) -> Result Result, String> { + audio::input_devices().map_err(|e| e.to_string()) +} + #[tauri::command] async fn save_config( app: tauri::AppHandle, @@ -1176,6 +1181,7 @@ pub fn run() { }) .invoke_handler(tauri::generate_handler![ get_config, + list_input_devices, save_config, toggle_dictation, current_backend, diff --git a/src/index.html b/src/index.html index e8efaee..842aeb8 100644 --- a/src/index.html +++ b/src/index.html @@ -172,8 +172,11 @@

Behavior

+

Microphones: checking…