Skip to repository content781 lines · 34.7 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:58:49.183Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
collab.rs
1use std::rc::Rc;
2use std::sync::Arc;
3
4use call::{ActiveCall, Room};
5use channel::ChannelStore;
6use client::{User, proto::PeerId};
7use gpui::{
8 AnyElement, Empty, Hsla, IntoElement, MouseButton, Path, ScreenCaptureSource, Styled, TaskExt,
9 WeakEntity, canvas, point,
10};
11use gpui::{App, Task, Window};
12use icons::IconName;
13use livekit_client::ConnectionQuality;
14use project::WorktreeSettings;
15use remote_connection::RemoteConnectionModal;
16use rpc::proto::{self};
17use settings::{Settings as _, SettingsLocation};
18use theme::ActiveTheme;
19use ui::{
20 Avatar, AvatarAudioStatusIndicator, ContextMenu, ContextMenuItem, Divider, DividerColor,
21 Facepile, KeyBinding, PopoverMenu, SplitButton, SplitButtonStyle, TintColor, Tooltip,
22 prelude::*,
23};
24use util::rel_path::RelPath;
25use workspace::{ParticipantLocation, notifications::DetachAndPromptErr};
26use zed_actions::ShowCallStats;
27
28use crate::TitleBar;
29
30fn format_stat(value: Option<f64>, format: impl Fn(f64) -> String) -> String {
31 match value {
32 Some(v) => format(v),
33 None => "—".to_string(),
34 }
35}
36
37pub fn toggle_screen_sharing(
38 screen: anyhow::Result<Option<Rc<dyn ScreenCaptureSource>>>,
39 window: &mut Window,
40 cx: &mut App,
41) {
42 let call = ActiveCall::global(cx).read(cx);
43 let toggle_screen_sharing = match screen {
44 Ok(screen) => {
45 let Some(room) = call.room().cloned() else {
46 return;
47 };
48
49 room.update(cx, |room, cx| {
50 let clicked_on_currently_shared_screen =
51 room.shared_screen_id().is_some_and(|screen_id| {
52 Some(screen_id)
53 == screen
54 .as_deref()
55 .and_then(|s| s.metadata().ok().map(|meta| meta.id))
56 });
57 let should_unshare_current_screen = room.is_sharing_screen();
58 let unshared_current_screen = should_unshare_current_screen.then(|| {
59 telemetry::event!(
60 "Screen Share Disabled",
61 room_id = room.id(),
62 channel_id = room.channel_id(),
63 );
64 room.unshare_screen(clicked_on_currently_shared_screen || screen.is_none(), cx)
65 });
66 if let Some(screen) = screen {
67 if !should_unshare_current_screen {
68 telemetry::event!(
69 "Screen Share Enabled",
70 room_id = room.id(),
71 channel_id = room.channel_id(),
72 );
73 }
74 cx.spawn(async move |room, cx| {
75 unshared_current_screen.transpose()?;
76 if !clicked_on_currently_shared_screen {
77 room.update(cx, |room, cx| room.share_screen(screen, cx))?
78 .await
79 } else {
80 Ok(())
81 }
82 })
83 } else {
84 Task::ready(Ok(()))
85 }
86 })
87 }
88 Err(e) => Task::ready(Err(e)),
89 };
90 toggle_screen_sharing.detach_and_prompt_err("Sharing Screen Failed", window, cx, |e, _, _| Some(format!("{:?}\n\nPlease check that you have given Omega permissions to record your screen in Settings.", e)));
91}
92
93pub fn toggle_mute(cx: &mut App) {
94 let call = ActiveCall::global(cx).read(cx);
95 if let Some(room) = call.room().cloned() {
96 room.update(cx, |room, cx| {
97 let operation = if room.is_muted() {
98 "Microphone Enabled"
99 } else {
100 "Microphone Disabled"
101 };
102 telemetry::event!(
103 operation,
104 room_id = room.id(),
105 channel_id = room.channel_id(),
106 );
107
108 room.toggle_mute(cx)
109 });
110 }
111}
112
113pub fn toggle_deafen(cx: &mut App) {
114 if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
115 room.update(cx, |room, cx| room.toggle_deafen(cx));
116 }
117}
118
119fn render_color_ribbon(color: Hsla) -> impl Element {
120 canvas(
121 move |_, _, _| {},
122 move |bounds, _, window, _| {
123 let height = bounds.size.height;
124 let horizontal_offset = height;
125 let vertical_offset = height / 2.0;
126 let mut path = Path::new(bounds.bottom_left());
127 path.curve_to(
128 bounds.origin + point(horizontal_offset, vertical_offset),
129 bounds.origin + point(px(0.0), vertical_offset),
130 );
131 path.line_to(bounds.top_right() + point(-horizontal_offset, vertical_offset));
132 path.curve_to(
133 bounds.bottom_right(),
134 bounds.top_right() + point(px(0.0), vertical_offset),
135 );
136 path.line_to(bounds.bottom_left());
137 window.paint_path(path, color);
138 },
139 )
140 .h_1()
141 .w_full()
142}
143
144impl TitleBar {
145 pub(crate) fn render_collaborator_list(
146 &self,
147 _: &mut Window,
148 cx: &mut Context<Self>,
149 ) -> impl IntoElement {
150 let room = ActiveCall::global(cx).read(cx).room().cloned();
151 let current_user = self.user_store.read(cx).current_user();
152 let client = self.client.clone();
153 let project_id = self.project.read(cx).remote_id();
154 let workspace = self.workspace.upgrade();
155
156 h_flex()
157 .id("collaborator-list")
158 .w_full()
159 .gap_1()
160 .overflow_x_scroll()
161 .when_some(
162 current_user.zip(client.peer_id()).zip(room),
163 |this, ((current_user, peer_id), room)| {
164 let player_colors = cx.theme().players();
165 let room = room.read(cx);
166 let mut remote_participants =
167 room.remote_participants().values().collect::<Vec<_>>();
168 remote_participants.sort_by_key(|p| p.participant_index.0);
169
170 let current_user_face_pile = self.render_collaborator(
171 ¤t_user,
172 peer_id,
173 true,
174 room.is_speaking(),
175 room.is_muted(),
176 None,
177 room,
178 project_id,
179 ¤t_user,
180 cx,
181 );
182
183 this.children(current_user_face_pile.map(|face_pile| {
184 v_flex()
185 .on_mouse_down(MouseButton::Left, |_, window, _| {
186 window.prevent_default()
187 })
188 .child(face_pile)
189 .child(render_color_ribbon(player_colors.local().cursor))
190 }))
191 .children(remote_participants.iter().filter_map(|collaborator| {
192 let player_color =
193 player_colors.color_for_participant(collaborator.participant_index.0);
194 let is_following = workspace
195 .as_ref()?
196 .read(cx)
197 .is_being_followed(collaborator.peer_id);
198 let is_present = project_id.is_some_and(|project_id| {
199 collaborator.location
200 == ParticipantLocation::SharedProject { project_id }
201 });
202
203 let facepile = self.render_collaborator(
204 &collaborator.user,
205 collaborator.peer_id,
206 is_present,
207 collaborator.speaking,
208 collaborator.muted,
209 is_following.then_some(player_color.selection),
210 room,
211 project_id,
212 ¤t_user,
213 cx,
214 )?;
215
216 Some(
217 v_flex()
218 .id(("collaborator", collaborator.user.legacy_id))
219 .child(facepile)
220 .child(render_color_ribbon(player_color.cursor))
221 .cursor_pointer()
222 .on_mouse_down(MouseButton::Left, |_, window, _| {
223 window.prevent_default()
224 })
225 .on_click({
226 let peer_id = collaborator.peer_id;
227 cx.listener(move |this, _, window, cx| {
228 cx.stop_propagation();
229
230 this.workspace
231 .update(cx, |workspace, cx| {
232 if is_following {
233 workspace.unfollow(peer_id, window, cx);
234 } else {
235 workspace.follow(peer_id, window, cx);
236 }
237 })
238 .ok();
239 })
240 })
241 .occlude()
242 .tooltip({
243 let login = collaborator.user.username.clone();
244 Tooltip::text(format!("Follow {login}"))
245 }),
246 )
247 }))
248 },
249 )
250 }
251
252 fn render_collaborator(
253 &self,
254 user: &Arc<User>,
255 peer_id: PeerId,
256 is_present: bool,
257 is_speaking: bool,
258 is_muted: bool,
259 leader_selection_color: Option<Hsla>,
260 room: &Room,
261 project_id: Option<u64>,
262 current_user: &Arc<User>,
263 cx: &App,
264 ) -> Option<Div> {
265 if room.role_for_user(user.legacy_id) == Some(proto::ChannelRole::Guest) {
266 return None;
267 }
268
269 const FACEPILE_LIMIT: usize = 3;
270 let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id));
271 let extra_count = followers.len().saturating_sub(FACEPILE_LIMIT);
272
273 Some(
274 div()
275 .m_0p5()
276 .p_0p5()
277 // When the collaborator is not followed, still draw this wrapper div, but leave
278 // it transparent, so that it does not shift the layout when following.
279 .when_some(leader_selection_color, |div, color| {
280 div.rounded_sm().bg(color)
281 })
282 .child(
283 Facepile::empty()
284 .child(
285 Avatar::new(user.avatar_uri.clone())
286 .grayscale(!is_present)
287 .border_color(if is_speaking {
288 cx.theme().status().info
289 } else {
290 // We draw the border in a transparent color rather to avoid
291 // the layout shift that would come with adding/removing the border.
292 gpui::transparent_black()
293 })
294 .when(is_muted, |avatar| {
295 avatar.indicator(
296 AvatarAudioStatusIndicator::new(ui::AudioStatus::Muted)
297 .tooltip({
298 let username = user.username.clone();
299 Tooltip::text(format!("{} is muted", username))
300 }),
301 )
302 }),
303 )
304 .children(followers.iter().take(FACEPILE_LIMIT).filter_map(
305 |follower_peer_id| {
306 let follower = room
307 .remote_participants()
308 .values()
309 .find_map(|p| {
310 (p.peer_id == *follower_peer_id).then_some(&p.user)
311 })
312 .or_else(|| {
313 (self.client.peer_id() == Some(*follower_peer_id))
314 .then_some(current_user)
315 })?
316 .clone();
317
318 Some(div().mt(-px(4.)).child(
319 Avatar::new(follower.avatar_uri.clone()).size(rems(0.75)),
320 ))
321 },
322 ))
323 .children(if extra_count > 0 {
324 Some(
325 Label::new(format!("+{extra_count}"))
326 .ml_1()
327 .into_any_element(),
328 )
329 } else {
330 None
331 }),
332 ),
333 )
334 }
335
336 pub(crate) fn render_call_controls(
337 &self,
338 window: &mut Window,
339 cx: &mut Context<Self>,
340 ) -> AnyElement {
341 let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() else {
342 return Empty.into_any_element();
343 };
344
345 let is_connecting_to_project = self
346 .workspace
347 .update(cx, |workspace, cx| {
348 workspace
349 .active_modal::<RemoteConnectionModal>(cx)
350 .is_some()
351 })
352 .unwrap_or(false);
353
354 let room = room.read(cx);
355 let project = self.project.read(cx);
356 let is_local = project.is_local() || project.is_via_remote_server();
357 let is_shared = is_local && project.is_shared();
358 let is_muted = room.is_muted();
359 let muted_by_user = room.muted_by_user();
360 let is_deafened = room.is_deafened().unwrap_or(false);
361 let is_screen_sharing = room.is_sharing_screen();
362 let can_use_microphone = room.can_use_microphone();
363 let can_share_projects = room.can_share_projects();
364 let screen_sharing_supported = cx.is_screen_capture_supported();
365
366 let stats = room
367 .diagnostics()
368 .map(|d| d.read(cx).stats().clone())
369 .unwrap_or_default();
370
371 let channel_store = ChannelStore::global(cx);
372 let channel = room
373 .channel_id()
374 .and_then(|channel_id| channel_store.read(cx).channel_for_id(channel_id).cloned());
375
376 let effective_quality = stats.effective_quality.unwrap_or(ConnectionQuality::Lost);
377 let (signal_icon, signal_color, quality_label) = match effective_quality {
378 ConnectionQuality::Excellent => {
379 (IconName::SignalHigh, Some(Color::Success), "Excellent")
380 }
381 ConnectionQuality::Good => (IconName::SignalHigh, None, "Good"),
382 ConnectionQuality::Poor => (IconName::SignalMedium, Some(Color::Warning), "Poor"),
383 ConnectionQuality::Lost => (IconName::SignalLow, Some(Color::Error), "Lost"),
384 };
385
386 let quality_label: SharedString = quality_label.into();
387
388 h_flex()
389 .gap_1()
390 .pr_1p5()
391 .child(
392 h_flex()
393 .gap_1()
394 .child(
395 IconButton::new("leave-call", IconName::Exit)
396 .tooltip(Tooltip::text("Leave Call"))
397 .icon_size(IconSize::Small)
398 .on_click(move |_, _window, cx| {
399 ActiveCall::global(cx)
400 .update(cx, |call, cx| call.hang_up(cx))
401 .detach_and_log_err(cx);
402 }),
403 )
404 .child(Divider::vertical().color(DividerColor::Border)),
405 )
406 .child(
407 IconButton::new("call-quality", signal_icon)
408 .icon_size(IconSize::Small)
409 .when_some(signal_color, |button, color| button.icon_color(color))
410 .tooltip(Tooltip::element(move |window, cx| {
411 let quality_label = quality_label.clone();
412 let latency = format_stat(stats.latency_ms, |v| format!("{:.0}ms", v));
413 let jitter = format_stat(stats.jitter_ms, |v| format!("{:.0}ms", v));
414 let packet_loss =
415 format_stat(stats.packet_loss_pct, |v| format!("{:.1}%", v));
416 let input_lag =
417 format_stat(stats.input_lag.map(|d| d.as_secs_f64() * 1000.0), |v| {
418 format!("{:.1}ms", v)
419 });
420
421 let key_binding = KeyBinding::for_action(&ShowCallStats, cx);
422 let has_key_binding = key_binding.has_binding(window);
423
424 let stat_row = |label: &'static str, value: String| {
425 h_flex()
426 .justify_between()
427 .gap_4()
428 .child(Label::new(label).size(LabelSize::Small).color(Color::Muted))
429 .child(Label::new(value).size(LabelSize::Small))
430 };
431
432 v_flex()
433 .child(
434 h_flex()
435 .gap_4()
436 .justify_between()
437 .child(Label::new(format!("Connection: {quality_label}")))
438 .when(has_key_binding, |this| this.child(key_binding)),
439 )
440 .child(
441 v_flex()
442 .gap_0p5()
443 .child(stat_row("Latency", latency))
444 .child(stat_row("Jitter", jitter))
445 .child(stat_row("Packet loss", packet_loss))
446 .child(stat_row("Input lag", input_lag)),
447 )
448 .into_any_element()
449 }))
450 .on_click(move |_, window, cx| {
451 window.dispatch_action(Box::new(ShowCallStats), cx);
452 }),
453 )
454 .when(can_use_microphone, |this| {
455 this.child(
456 IconButton::new(
457 "mute-microphone",
458 if is_muted {
459 IconName::MicMute
460 } else {
461 IconName::Mic
462 },
463 )
464 .tooltip(move |_window, cx| {
465 if is_muted {
466 if is_deafened {
467 Tooltip::with_meta(
468 "Unmute Microphone",
469 None,
470 "Audio will be unmuted",
471 cx,
472 )
473 } else {
474 Tooltip::simple("Unmute Microphone", cx)
475 }
476 } else {
477 Tooltip::simple("Mute Microphone", cx)
478 }
479 })
480 .icon_size(IconSize::Small)
481 .toggle_state(is_muted)
482 .selected_style(ButtonStyle::Tinted(TintColor::Error))
483 .on_click(move |_, _window, cx| toggle_mute(cx)),
484 )
485 })
486 .child(
487 IconButton::new(
488 "mute-sound",
489 if is_deafened {
490 IconName::AudioOff
491 } else {
492 IconName::AudioOn
493 },
494 )
495 .selected_style(ButtonStyle::Tinted(TintColor::Error))
496 .icon_size(IconSize::Small)
497 .toggle_state(is_deafened)
498 .tooltip(move |_window, cx| {
499 if is_deafened {
500 let label = "Unmute Audio";
501
502 if !muted_by_user {
503 Tooltip::with_meta(label, None, "Microphone will be unmuted", cx)
504 } else {
505 Tooltip::simple(label, cx)
506 }
507 } else {
508 let label = "Mute Audio";
509
510 if !muted_by_user {
511 Tooltip::with_meta(label, None, "Microphone will be muted", cx)
512 } else {
513 Tooltip::simple(label, cx)
514 }
515 }
516 })
517 .on_click(move |_, _, cx| toggle_deafen(cx)),
518 )
519 .when(
520 is_local && can_share_projects && !is_connecting_to_project,
521 |this| {
522 let is_sharing_disabled =
523 channel.is_some_and(|channel| match channel.visibility {
524 proto::ChannelVisibility::Public => {
525 project.visible_worktrees(cx).any(|worktree| {
526 let worktree_id = worktree.read(cx).id();
527
528 let settings_location = Some(SettingsLocation {
529 worktree_id,
530 path: RelPath::empty(),
531 });
532
533 WorktreeSettings::get(settings_location, cx)
534 .prevent_sharing_in_public_channels
535 })
536 }
537 proto::ChannelVisibility::Members => false,
538 });
539
540 let icon = if is_shared {
541 IconName::FolderShared
542 } else {
543 IconName::FolderShare
544 };
545
546 let folder_names = project
547 .visible_worktrees(cx)
548 .filter_map(|worktree| {
549 worktree
550 .read(cx)
551 .root_name()
552 .file_name()
553 .map(|name| name.to_string())
554 })
555 .collect::<Vec<_>>();
556 let folder_list = folder_names.join(", ");
557
558 let unshare_meta: SharedString = if folder_list.is_empty() {
559 "Stop sharing project with call participants".into()
560 } else {
561 format!("Stop sharing {folder_list} with call participants").into()
562 };
563 let share_meta: SharedString = if folder_list.is_empty() {
564 "Share active project with call participants".into()
565 } else {
566 format!("Share {folder_list} with call participants").into()
567 };
568
569 this.child(
570 IconButton::new("toggle_sharing", icon)
571 .icon_size(IconSize::Small)
572 .selected_style(ButtonStyle::Tinted(TintColor::Accent))
573 .toggle_state(is_shared)
574 .map(|this| {
575 if is_shared {
576 this.tooltip(move |_, cx| {
577 Tooltip::with_meta(
578 "Unshare Project",
579 None,
580 unshare_meta.clone(),
581 cx,
582 )
583 })
584 .on_click(cx.listener(
585 move |this, _, window, cx| {
586 this.unshare_project(window, cx);
587 },
588 ))
589 } else if is_sharing_disabled {
590 this.disabled(true).tooltip(Tooltip::text(
591 "This project may not be shared in a public channel.",
592 ))
593 } else {
594 this.tooltip(move |_, cx| {
595 Tooltip::with_meta(
596 "Share Project",
597 None,
598 share_meta.clone(),
599 cx,
600 )
601 })
602 .on_click(cx.listener(
603 move |this, _, _, cx| {
604 this.share_project(cx);
605 },
606 ))
607 }
608 }),
609 )
610 },
611 )
612 .when(can_use_microphone && screen_sharing_supported, |parent| {
613 #[cfg(target_os = "linux")]
614 let is_wayland = gpui::guess_compositor() == "Wayland";
615 #[cfg(not(target_os = "linux"))]
616 let is_wayland = false;
617
618 let trigger = IconButton::new("screen-share", IconName::Screen)
619 .style(ButtonStyle::Subtle)
620 .icon_size(IconSize::Small)
621 .toggle_state(is_screen_sharing)
622 .selected_style(ButtonStyle::Tinted(TintColor::Accent))
623 .tooltip(Tooltip::text(if is_screen_sharing {
624 "Stop Sharing Screen"
625 } else {
626 "Share Screen"
627 }))
628 .on_click(move |_, window, cx| {
629 let should_share = ActiveCall::global(cx)
630 .read(cx)
631 .room()
632 .is_some_and(|room| !room.read(cx).is_sharing_screen());
633
634 #[cfg(target_os = "linux")]
635 {
636 if is_wayland
637 && let Some(room) = ActiveCall::global(cx).read(cx).room().cloned()
638 {
639 let task = room.update(cx, |room, cx| {
640 if should_share {
641 room.share_screen_wayland(cx)
642 } else {
643 room.unshare_screen(true, cx)
644 .map(|()| Task::ready(Ok(())))
645 .unwrap_or_else(|e| Task::ready(Err(e)))
646 }
647 });
648 task.detach_and_prompt_err(
649 "Sharing Screen Failed",
650 window,
651 cx,
652 |e, _, _| Some(format!("{e:?}")),
653 );
654 }
655 }
656 if !is_wayland {
657 window
658 .spawn(cx, async move |cx| {
659 let screen = if should_share {
660 cx.update(|_, cx| pick_default_screen(cx))?.await
661 } else {
662 Ok(None)
663 };
664 cx.update(|window, cx| {
665 toggle_screen_sharing(screen, window, cx)
666 })?;
667
668 Result::<_, anyhow::Error>::Ok(())
669 })
670 .detach();
671 }
672 });
673
674 if is_wayland {
675 parent.child(trigger)
676 } else {
677 parent.child(
678 SplitButton::new(
679 trigger.render(window, cx),
680 self.render_screen_list().into_any_element(),
681 )
682 .style(SplitButtonStyle::Transparent),
683 )
684 }
685 })
686 .into_any_element()
687 }
688
689 fn render_screen_list(&self) -> impl IntoElement {
690 PopoverMenu::new("screen-share-screen-list")
691 .with_handle(self.screen_share_popover_handle.clone())
692 .trigger(
693 ui::ButtonLike::new_rounded_right("screen-share-screen-list-trigger")
694 .child(
695 h_flex()
696 .mx_neg_0p5()
697 .h_full()
698 .justify_center()
699 .child(Icon::new(IconName::ChevronDown).size(IconSize::XSmall)),
700 )
701 .toggle_state(self.screen_share_popover_handle.is_deployed()),
702 )
703 .menu(|window, cx| {
704 let screens = cx.screen_capture_sources();
705 Some(ContextMenu::build(window, cx, |context_menu, _, cx| {
706 cx.spawn(async move |this: WeakEntity<ContextMenu>, cx| {
707 let screens = screens.await??;
708 this.update(cx, |this, cx| {
709 let active_screenshare_id = ActiveCall::global(cx)
710 .read(cx)
711 .room()
712 .and_then(|room| room.read(cx).shared_screen_id());
713 for screen in screens {
714 let Ok(meta) = screen.metadata() else {
715 continue;
716 };
717
718 let label = meta
719 .label
720 .clone()
721 .unwrap_or_else(|| SharedString::from("Unknown screen"));
722 let resolution = SharedString::from(format!(
723 "{} × {}",
724 meta.resolution.width.0, meta.resolution.height.0
725 ));
726 this.push_item(ContextMenuItem::CustomEntry {
727 entry_render: Box::new(move |_, _| {
728 h_flex()
729 .gap_2()
730 .child(
731 Icon::new(IconName::Screen)
732 .size(IconSize::XSmall)
733 .map(|this| {
734 if active_screenshare_id == Some(meta.id) {
735 this.color(Color::Accent)
736 } else {
737 this.color(Color::Muted)
738 }
739 }),
740 )
741 .child(Label::new(label.clone()))
742 .child(
743 Label::new(resolution.clone())
744 .color(Color::Muted)
745 .size(LabelSize::Small),
746 )
747 .into_any()
748 }),
749 selectable: true,
750 documentation_aside: None,
751 handler: Rc::new(move |_, window, cx| {
752 toggle_screen_sharing(Ok(Some(screen.clone())), window, cx);
753 }),
754 });
755 }
756 })
757 })
758 .detach_and_log_err(cx);
759 context_menu
760 }))
761 })
762 }
763}
764
765/// Picks the screen to share when clicking on the main screen sharing button.
766fn pick_default_screen(cx: &App) -> Task<anyhow::Result<Option<Rc<dyn ScreenCaptureSource>>>> {
767 let source = cx.screen_capture_sources();
768 cx.spawn(async move |_| {
769 let available_sources = source.await??;
770 Ok(available_sources
771 .iter()
772 .find(|it| {
773 it.as_ref()
774 .metadata()
775 .is_ok_and(|meta| meta.is_main.unwrap_or_default())
776 })
777 .or_else(|| available_sources.first())
778 .cloned())
779 })
780}
781