audio_input_output_setup.rs

  1use audio::{AudioDeviceInfo, AvailableAudioDevices};
  2use cpal::DeviceId;
  3use gpui::{AnyElement, App, ElementId, ReadGlobal, SharedString, Window};
  4use settings::{AudioInputDeviceName, AudioOutputDeviceName, SettingsStore};
  5use std::str::FromStr;
  6use ui::{ContextMenu, DropdownMenu, DropdownStyle, IconPosition, IntoElement};
  7use util::ResultExt;
  8
  9use crate::{SettingField, SettingsFieldMetadata, SettingsUiFile, update_settings_file};
 10
 11pub(crate) const SYSTEM_DEFAULT: &str = "System Default";
 12
 13pub(crate) fn get_current_device(
 14    current_id: Option<&DeviceId>,
 15    is_input: bool,
 16    devices: &[AudioDeviceInfo],
 17) -> Option<AudioDeviceInfo> {
 18    let Some(current_id) = current_id else {
 19        return None;
 20    };
 21    devices
 22        .iter()
 23        .find(|d| d.matches(current_id, is_input))
 24        .cloned()
 25}
 26
 27pub(crate) fn render_audio_device_dropdown<F>(
 28    dropdown_id: impl Into<ElementId>,
 29    current_device_id: Option<DeviceId>,
 30    is_input: bool,
 31    on_select: F,
 32    window: &mut Window,
 33    cx: &mut App,
 34) -> AnyElement
 35where
 36    F: Fn(Option<DeviceId>, &mut Window, &mut App) + Clone + 'static,
 37{
 38    audio::ensure_devices_initialized(cx);
 39    let devices = cx.global::<AvailableAudioDevices>().0.clone();
 40    let current_device = get_current_device(current_device_id.as_ref(), is_input, &devices);
 41
 42    let menu = ContextMenu::build(window, cx, {
 43        let current_device = current_device.clone();
 44        move |mut menu, _, _cx| {
 45            let is_system_default = current_device.is_none();
 46            menu = menu.toggleable_entry(
 47                SYSTEM_DEFAULT,
 48                is_system_default,
 49                IconPosition::Start,
 50                None,
 51                {
 52                    let on_select = on_select.clone();
 53                    move |window, cx| {
 54                        on_select(None, window, cx);
 55                    }
 56                },
 57            );
 58
 59            for device in devices.iter().filter(|d| d.matches_input(is_input)) {
 60                let is_current = current_device
 61                    .as_ref()
 62                    .map(|info| info.matches(&device.id, is_input))
 63                    .unwrap_or(false);
 64                let device_id = device.id.clone();
 65
 66                menu = menu.toggleable_entry(
 67                    device.to_string(),
 68                    is_current,
 69                    IconPosition::Start,
 70                    None,
 71                    {
 72                        let on_select = on_select.clone();
 73                        move |window, cx| {
 74                            on_select(Some(device_id.clone()), window, cx);
 75                        }
 76                    },
 77                );
 78            }
 79            menu
 80        }
 81    });
 82
 83    DropdownMenu::new(
 84        dropdown_id,
 85        current_device
 86            .map(|info| info.desc.name().to_string())
 87            .unwrap_or(SYSTEM_DEFAULT.to_string()),
 88        menu,
 89    )
 90    .style(DropdownStyle::Outlined)
 91    .full_width(true)
 92    .into_any_element()
 93}
 94
 95fn render_settings_audio_device_dropdown<T: AsRef<Option<String>> + From<Option<String>> + Send>(
 96    field: SettingField<T>,
 97    file: SettingsUiFile,
 98    is_input: bool,
 99    window: &mut Window,
100    cx: &mut App,
101) -> AnyElement {
102    let (_, current_value): (_, Option<&T>) =
103        SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
104    let current_device_id =
105        current_value.and_then(|x| x.as_ref().clone().and_then(|x| DeviceId::from_str(&x).ok()));
106
107    let dropdown_id: SharedString = if is_input {
108        "input-audio-device-dropdown".into()
109    } else {
110        "output-audio-device-dropdown".into()
111    };
112
113    render_audio_device_dropdown(
114        dropdown_id,
115        current_device_id,
116        is_input,
117        move |device_id, window, cx| {
118            let value: Option<T> = device_id.map(|id| T::from(Some(id.to_string())));
119            update_settings_file(
120                file.clone(),
121                field.json_path,
122                window,
123                cx,
124                move |settings, _cx| {
125                    (field.write)(settings, value);
126                },
127            )
128            .log_err();
129        },
130        window,
131        cx,
132    )
133}
134
135pub fn render_input_audio_device_dropdown(
136    field: SettingField<AudioInputDeviceName>,
137    file: SettingsUiFile,
138    _metadata: Option<&SettingsFieldMetadata>,
139    window: &mut Window,
140    cx: &mut App,
141) -> AnyElement {
142    render_settings_audio_device_dropdown(field, file, true, window, cx)
143}
144
145pub fn render_output_audio_device_dropdown(
146    field: SettingField<AudioOutputDeviceName>,
147    file: SettingsUiFile,
148    _metadata: Option<&SettingsFieldMetadata>,
149    window: &mut Window,
150    cx: &mut App,
151) -> AnyElement {
152    render_settings_audio_device_dropdown(field, file, false, window, cx)
153}