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    let devices = cx.default_global::<AvailableAudioDevices>().0.clone();
 39    let current_device = get_current_device(current_device_id.as_ref(), is_input, &devices);
 40
 41    let menu = ContextMenu::build(window, cx, {
 42        let current_device = current_device.clone();
 43        move |mut menu, _, _cx| {
 44            let is_system_default = current_device.is_none();
 45            menu = menu.toggleable_entry(
 46                SYSTEM_DEFAULT,
 47                is_system_default,
 48                IconPosition::Start,
 49                None,
 50                {
 51                    let on_select = on_select.clone();
 52                    move |window, cx| {
 53                        on_select(None, window, cx);
 54                    }
 55                },
 56            );
 57
 58            for device in devices.iter().filter(|d| d.matches_input(is_input)) {
 59                let is_current = current_device
 60                    .as_ref()
 61                    .map(|info| info.matches(&device.id, is_input))
 62                    .unwrap_or(false);
 63                let device_id = device.id.clone();
 64
 65                menu = menu.toggleable_entry(
 66                    device.to_string(),
 67                    is_current,
 68                    IconPosition::Start,
 69                    None,
 70                    {
 71                        let on_select = on_select.clone();
 72                        move |window, cx| {
 73                            on_select(Some(device_id.clone()), window, cx);
 74                        }
 75                    },
 76                );
 77            }
 78            menu
 79        }
 80    });
 81
 82    DropdownMenu::new(
 83        dropdown_id,
 84        current_device
 85            .map(|info| info.desc.name().to_string())
 86            .unwrap_or(SYSTEM_DEFAULT.to_string()),
 87        menu,
 88    )
 89    .style(DropdownStyle::Outlined)
 90    .full_width(true)
 91    .into_any_element()
 92}
 93
 94fn render_settings_audio_device_dropdown<T: AsRef<Option<String>> + From<Option<String>> + Send>(
 95    field: SettingField<T>,
 96    file: SettingsUiFile,
 97    is_input: bool,
 98    window: &mut Window,
 99    cx: &mut App,
100) -> AnyElement {
101    let (_, current_value): (_, Option<&T>) =
102        SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick);
103    let current_device_id =
104        current_value.and_then(|x| x.as_ref().clone().and_then(|x| DeviceId::from_str(&x).ok()));
105
106    let dropdown_id: SharedString = if is_input {
107        "input-audio-device-dropdown".into()
108    } else {
109        "output-audio-device-dropdown".into()
110    };
111
112    render_audio_device_dropdown(
113        dropdown_id,
114        current_device_id,
115        is_input,
116        move |device_id, window, cx| {
117            let value: Option<T> = device_id.map(|id| T::from(Some(id.to_string())));
118            update_settings_file(
119                file.clone(),
120                field.json_path,
121                window,
122                cx,
123                move |settings, _cx| {
124                    (field.write)(settings, value);
125                },
126            )
127            .log_err();
128        },
129        window,
130        cx,
131    )
132}
133
134pub fn render_input_audio_device_dropdown(
135    field: SettingField<AudioInputDeviceName>,
136    file: SettingsUiFile,
137    _metadata: Option<&SettingsFieldMetadata>,
138    window: &mut Window,
139    cx: &mut App,
140) -> AnyElement {
141    render_settings_audio_device_dropdown(field, file, true, window, cx)
142}
143
144pub fn render_output_audio_device_dropdown(
145    field: SettingField<AudioOutputDeviceName>,
146    file: SettingsUiFile,
147    _metadata: Option<&SettingsFieldMetadata>,
148    window: &mut Window,
149    cx: &mut App,
150) -> AnyElement {
151    render_settings_audio_device_dropdown(field, file, false, window, cx)
152}