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}