Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:58:49.183Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

collab.rs

781 lines · 34.7 KB · rust
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                        &current_user,
172                        peer_id,
173                        true,
174                        room.is_speaking(),
175                        room.is_muted(),
176                        None,
177                        room,
178                        project_id,
179                        &current_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                            &current_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
Served at tenant.openagents/omega Member data and write actions are omitted.