From 959b2961ffdcb085aa414c6b10714f49aa46001a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 4 Dec 2023 16:55:04 -0800 Subject: [PATCH] Revert "Decouple workspace from call (#3380)" This reverts commit 6da57cbc6e33a7d1ede533ecc11948292feb22f3, reversing changes made to 62b18437044c9e2b7e4ef2ba24fbbf12444a48c7. Also, adjust new code that was written using the "call handler". --- Cargo.lock | 4 - crates/call2/Cargo.toml | 4 +- crates/call2/src/call2.rs | 257 +------- crates/call2/src/participant.rs | 2 +- crates/collab2/src/tests/channel_tests.rs | 37 +- crates/collab2/src/tests/integration_tests.rs | 11 +- crates/collab2/src/tests/test_server.rs | 1 - crates/collab_ui2/src/collab_panel.rs | 32 +- crates/collab_ui2/src/collab_titlebar_item.rs | 128 ++-- crates/collab_ui2/src/collab_ui.rs | 115 ++-- crates/workspace2/Cargo.toml | 2 +- crates/workspace2/src/pane_group.rs | 4 + .../src/shared_screen.rs | 9 +- crates/workspace2/src/workspace2.rs | 613 ++++++++---------- crates/zed2/src/main.rs | 1 - 15 files changed, 423 insertions(+), 797 deletions(-) rename crates/{call2 => workspace2}/src/shared_screen.rs (94%) diff --git a/Cargo.lock b/Cargo.lock index 03945e4578..fe126973af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1222,7 +1222,6 @@ version = "0.1.0" dependencies = [ "anyhow", "async-broadcast", - "async-trait", "audio2", "client2", "collections", @@ -1242,9 +1241,7 @@ dependencies = [ "serde_json", "settings2", "smallvec", - "ui2", "util", - "workspace2", ] [[package]] @@ -11477,7 +11474,6 @@ version = "0.1.0" dependencies = [ "anyhow", "async-recursion 1.0.5", - "async-trait", "bincode", "call2", "client2", diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index 8dc37f68dd..c2d95c8b52 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -31,9 +31,7 @@ media = { path = "../media" } project = { package = "project2", path = "../project2" } settings = { package = "settings2", path = "../settings2" } util = { path = "../util" } -ui = {package = "ui2", path = "../ui2"} -workspace = {package = "workspace2", path = "../workspace2"} -async-trait.workspace = true + anyhow.workspace = true async-broadcast = "0.4" futures.workspace = true diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index a933057723..14cb28c32d 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -1,32 +1,25 @@ pub mod call_settings; pub mod participant; pub mod room; -mod shared_screen; use anyhow::{anyhow, Result}; -use async_trait::async_trait; use audio::Audio; use call_settings::CallSettings; -use client::{ - proto::{self, PeerId}, - Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE, -}; +use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ - AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, PromptLevel, - Subscription, Task, View, ViewContext, VisualContext, WeakModel, WindowHandle, + AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task, + WeakModel, }; -pub use participant::ParticipantLocation; use postage::watch; use project::Project; use room::Event; -pub use room::Room; use settings::Settings; -use shared_screen::SharedScreen; use std::sync::Arc; -use util::ResultExt; -use workspace::{item::ItemHandle, CallHandler, Pane, Workspace}; + +pub use participant::ParticipantLocation; +pub use room::Room; pub fn init(client: Arc, user_store: Model, cx: &mut AppContext) { CallSettings::register(cx); @@ -334,55 +327,12 @@ impl ActiveCall { pub fn join_channel( &mut self, channel_id: u64, - requesting_window: Option>, cx: &mut ModelContext, ) -> Task>>> { if let Some(room) = self.room().cloned() { if room.read(cx).channel_id() == Some(channel_id) { - return cx.spawn(|_, _| async move { - todo!(); - // let future = room.update(&mut cx, |room, cx| { - // room.most_active_project(cx).map(|(host, project)| { - // room.join_project(project, host, app_state.clone(), cx) - // }) - // }) - - // if let Some(future) = future { - // future.await?; - // } - - // Ok(Some(room)) - }); - } - - let should_prompt = room.update(cx, |room, _| { - room.channel_id().is_some() - && room.is_sharing_project() - && room.remote_participants().len() > 0 - }); - if should_prompt && requesting_window.is_some() { - return cx.spawn(|this, mut cx| async move { - let answer = requesting_window.unwrap().update(&mut cx, |_, cx| { - cx.prompt( - PromptLevel::Warning, - "Leaving this call will unshare your current project.\nDo you want to switch channels?", - &["Yes, Join Channel", "Cancel"], - ) - })?; - if answer.await? == 1 { - return Ok(None); - } - - room.update(&mut cx, |room, cx| room.clear_state(cx))?; - - this.update(&mut cx, |this, cx| { - this.join_channel(channel_id, requesting_window, cx) - })? - .await - }); - } - - if room.read(cx).channel_id().is_some() { + return Task::ready(Ok(Some(room))); + } else { room.update(cx, |room, cx| room.clear_state(cx)); } } @@ -555,197 +505,6 @@ pub fn report_call_event_for_channel( ) } -pub struct Call { - active_call: Option<(Model, Vec)>, -} - -impl Call { - pub fn new(cx: &mut ViewContext<'_, Workspace>) -> Box { - let mut active_call = None; - if cx.has_global::>() { - let call = cx.global::>().clone(); - let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; - active_call = Some((call, subscriptions)); - } - Box::new(Self { active_call }) - } - fn on_active_call_event( - workspace: &mut Workspace, - _: Model, - event: &room::Event, - cx: &mut ViewContext, - ) { - match event { - room::Event::ParticipantLocationChanged { participant_id } - | room::Event::RemoteVideoTracksChanged { participant_id } => { - workspace.leader_updated(*participant_id, cx); - } - _ => {} - } - } -} - -#[async_trait(?Send)] -impl CallHandler for Call { - fn peer_state( - &mut self, - leader_id: PeerId, - project: &Model, - cx: &mut ViewContext, - ) -> Option<(bool, bool)> { - let (call, _) = self.active_call.as_ref()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(leader_id)?; - - let leader_in_this_app; - let leader_in_this_project; - match participant.location { - ParticipantLocation::SharedProject { project_id } => { - leader_in_this_app = true; - leader_in_this_project = Some(project_id) == project.read(cx).remote_id(); - } - ParticipantLocation::UnsharedProject => { - leader_in_this_app = true; - leader_in_this_project = false; - } - ParticipantLocation::External => { - leader_in_this_app = false; - leader_in_this_project = false; - } - }; - - Some((leader_in_this_project, leader_in_this_app)) - } - - fn shared_screen_for_peer( - &self, - peer_id: PeerId, - pane: &View, - cx: &mut ViewContext, - ) -> Option> { - let (call, _) = self.active_call.as_ref()?; - let room = call.read(cx).room()?.read(cx); - let participant = room.remote_participant_for_peer_id(peer_id)?; - let track = participant.video_tracks.values().next()?.clone(); - let user = participant.user.clone(); - for item in pane.read(cx).items_of_type::() { - if item.read(cx).peer_id == peer_id { - return Some(Box::new(item)); - } - } - - Some(Box::new(cx.build_view(|cx| { - SharedScreen::new(&track, peer_id, user.clone(), cx) - }))) - } - fn room_id(&self, cx: &AppContext) -> Option { - Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id()) - } - fn hang_up(&self, cx: &mut AppContext) -> Task> { - let Some((call, _)) = self.active_call.as_ref() else { - return Task::ready(Err(anyhow!("Cannot exit a call; not in a call"))); - }; - - call.update(cx, |this, cx| this.hang_up(cx)) - } - fn active_project(&self, cx: &AppContext) -> Option> { - ActiveCall::global(cx).read(cx).location().cloned() - } - fn invite( - &mut self, - called_user_id: u64, - initial_project: Option>, - cx: &mut AppContext, - ) -> Task> { - ActiveCall::global(cx).update(cx, |this, cx| { - this.invite(called_user_id, initial_project, cx) - }) - } - fn remote_participants(&self, cx: &AppContext) -> Option, PeerId)>> { - self.active_call - .as_ref() - .map(|call| { - call.0.read(cx).room().map(|room| { - room.read(cx) - .remote_participants() - .iter() - .map(|participant| { - (participant.1.user.clone(), participant.1.peer_id.clone()) - }) - .collect() - }) - }) - .flatten() - } - fn is_muted(&self, cx: &AppContext) -> Option { - self.active_call - .as_ref() - .map(|call| { - call.0 - .read(cx) - .room() - .map(|room| room.read(cx).is_muted(cx)) - }) - .flatten() - } - fn toggle_mute(&self, cx: &mut AppContext) { - self.active_call.as_ref().map(|call| { - call.0.update(cx, |this, cx| { - this.room().map(|room| { - let room = room.clone(); - cx.spawn(|_, mut cx| async move { - room.update(&mut cx, |this, cx| this.toggle_mute(cx))?? - .await - }) - .detach_and_log_err(cx); - }) - }) - }); - } - fn toggle_screen_share(&self, cx: &mut AppContext) { - self.active_call.as_ref().map(|call| { - call.0.update(cx, |this, cx| { - this.room().map(|room| { - room.update(cx, |this, cx| { - if this.is_screen_sharing() { - this.unshare_screen(cx).log_err(); - } else { - let t = this.share_screen(cx); - cx.spawn(move |_, _| async move { - t.await.log_err(); - }) - .detach(); - } - }) - }) - }) - }); - } - fn toggle_deafen(&self, cx: &mut AppContext) { - self.active_call.as_ref().map(|call| { - call.0.update(cx, |this, cx| { - this.room().map(|room| { - room.update(cx, |this, cx| { - this.toggle_deafen(cx).log_err(); - }) - }) - }) - }); - } - fn is_deafened(&self, cx: &AppContext) -> Option { - self.active_call - .as_ref() - .map(|call| { - call.0 - .read(cx) - .room() - .map(|room| room.read(cx).is_deafened()) - }) - .flatten() - .flatten() - } -} - #[cfg(test)] mod test { use gpui::TestAppContext; diff --git a/crates/call2/src/participant.rs b/crates/call2/src/participant.rs index 325a4f812b..11a58b4b09 100644 --- a/crates/call2/src/participant.rs +++ b/crates/call2/src/participant.rs @@ -4,7 +4,7 @@ use client::{proto, User}; use collections::HashMap; use gpui::WeakModel; pub use live_kit_client::Frame; -pub(crate) use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; +pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; use project::Project; use std::sync::Arc; diff --git a/crates/collab2/src/tests/channel_tests.rs b/crates/collab2/src/tests/channel_tests.rs index 43d18ee7d1..8ce5d99b80 100644 --- a/crates/collab2/src/tests/channel_tests.rs +++ b/crates/collab2/src/tests/channel_tests.rs @@ -364,8 +364,7 @@ async fn test_joining_channel_ancestor_member( let active_call_b = cx_b.read(ActiveCall::global); assert!(active_call_b - .update(cx_b, |active_call, cx| active_call - .join_channel(sub_id, None, cx)) + .update(cx_b, |active_call, cx| active_call.join_channel(sub_id, cx)) .await .is_ok()); } @@ -395,9 +394,7 @@ async fn test_channel_room( let active_call_b = cx_b.read(ActiveCall::global); active_call_a - .update(cx_a, |active_call, cx| { - active_call.join_channel(zed_id, None, cx) - }) + .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx)) .await .unwrap(); @@ -445,9 +442,7 @@ async fn test_channel_room( }); active_call_b - .update(cx_b, |active_call, cx| { - active_call.join_channel(zed_id, None, cx) - }) + .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx)) .await .unwrap(); @@ -564,16 +559,12 @@ async fn test_channel_room( }); active_call_a - .update(cx_a, |active_call, cx| { - active_call.join_channel(zed_id, None, cx) - }) + .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx)) .await .unwrap(); active_call_b - .update(cx_b, |active_call, cx| { - active_call.join_channel(zed_id, None, cx) - }) + .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx)) .await .unwrap(); @@ -617,9 +608,7 @@ async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppCo let active_call_a = cx_a.read(ActiveCall::global); active_call_a - .update(cx_a, |active_call, cx| { - active_call.join_channel(zed_id, None, cx) - }) + .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx)) .await .unwrap(); @@ -638,7 +627,7 @@ async fn test_channel_jumping(executor: BackgroundExecutor, cx_a: &mut TestAppCo active_call_a .update(cx_a, |active_call, cx| { - active_call.join_channel(rust_id, None, cx) + active_call.join_channel(rust_id, cx) }) .await .unwrap(); @@ -804,7 +793,7 @@ async fn test_call_from_channel( let active_call_b = cx_b.read(ActiveCall::global); active_call_a - .update(cx_a, |call, cx| call.join_channel(channel_id, None, cx)) + .update(cx_a, |call, cx| call.join_channel(channel_id, cx)) .await .unwrap(); @@ -1297,7 +1286,7 @@ async fn test_guest_access( // Non-members should not be allowed to join assert!(active_call_b - .update(cx_b, |call, cx| call.join_channel(channel_a, None, cx)) + .update(cx_b, |call, cx| call.join_channel(channel_a, cx)) .await .is_err()); @@ -1319,7 +1308,7 @@ async fn test_guest_access( // Client B joins channel A as a guest active_call_b - .update(cx_b, |call, cx| call.join_channel(channel_a, None, cx)) + .update(cx_b, |call, cx| call.join_channel(channel_a, cx)) .await .unwrap(); @@ -1352,7 +1341,7 @@ async fn test_guest_access( assert_channels_list_shape(client_b.channel_store(), cx_b, &[]); active_call_b - .update(cx_b, |call, cx| call.join_channel(channel_b, None, cx)) + .update(cx_b, |call, cx| call.join_channel(channel_b, cx)) .await .unwrap(); @@ -1383,7 +1372,7 @@ async fn test_invite_access( // should not be allowed to join assert!(active_call_b - .update(cx_b, |call, cx| call.join_channel(channel_b_id, None, cx)) + .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx)) .await .is_err()); @@ -1401,7 +1390,7 @@ async fn test_invite_access( .unwrap(); active_call_b - .update(cx_b, |call, cx| call.join_channel(channel_b_id, None, cx)) + .update(cx_b, |call, cx| call.join_channel(channel_b_id, cx)) .await .unwrap(); diff --git a/crates/collab2/src/tests/integration_tests.rs b/crates/collab2/src/tests/integration_tests.rs index 2268a51f2b..7104d36b8d 100644 --- a/crates/collab2/src/tests/integration_tests.rs +++ b/crates/collab2/src/tests/integration_tests.rs @@ -510,10 +510,9 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously( // Simultaneously join channel 1 and then channel 2 active_call_a - .update(cx_a, |call, cx| call.join_channel(channel_1, None, cx)) + .update(cx_a, |call, cx| call.join_channel(channel_1, cx)) .detach(); - let join_channel_2 = - active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, None, cx)); + let join_channel_2 = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_2, cx)); join_channel_2.await.unwrap(); @@ -539,8 +538,7 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously( call.invite(client_c.user_id().unwrap(), None, cx) }); - let join_channel = - active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx)); + let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx)); b_invite.await.unwrap(); c_invite.await.unwrap(); @@ -569,8 +567,7 @@ async fn test_joining_channels_and_calling_multiple_users_simultaneously( .unwrap(); // Simultaneously join channel 1 and call user B and user C from client A. - let join_channel = - active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, None, cx)); + let join_channel = active_call_a.update(cx_a, |call, cx| call.join_channel(channel_1, cx)); let b_invite = active_call_a.update(cx_a, |call, cx| { call.invite(client_b.user_id().unwrap(), None, cx) diff --git a/crates/collab2/src/tests/test_server.rs b/crates/collab2/src/tests/test_server.rs index 5f95f00d6f..6bb57e11ab 100644 --- a/crates/collab2/src/tests/test_server.rs +++ b/crates/collab2/src/tests/test_server.rs @@ -221,7 +221,6 @@ impl TestServer { fs: fs.clone(), build_window_options: |_, _, _| Default::default(), node_runtime: FakeNodeRuntime::new(), - call_factory: |_| Box::new(workspace::TestCallHandler), }); cx.update(|cx| { diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 03637a051f..9144298897 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -1199,7 +1199,6 @@ impl CollabPanel { .on_click(cx.listener(move |this, _, cx| { this.workspace.update(cx, |workspace, cx| { let app_state = workspace.app_state().clone(); - let call = workspace.call_state(); workspace::join_remote_project(project_id, host_user_id, app_state, cx) .detach_and_log_err(cx); }); @@ -2219,20 +2218,19 @@ impl CollabPanel { } fn join_channel(&self, channel_id: u64, cx: &mut ViewContext) { + let Some(workspace) = self.workspace.upgrade() else { + return; + }; let Some(handle) = cx.window_handle().downcast::() else { return; }; - let active_call = ActiveCall::global(cx); - cx.spawn(|_, mut cx| async move { - active_call - .update(&mut cx, |active_call, cx| { - active_call.join_channel(channel_id, Some(handle), cx) - }) - .log_err()? - .await - .notify_async_err(&mut cx) - }) - .detach() + workspace::join_channel( + channel_id, + workspace.read(cx).app_state().clone(), + Some(handle), + cx, + ) + .detach_and_log_err(cx) } fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { @@ -2500,15 +2498,7 @@ impl CollabPanel { let user_id = contact.user.id; let github_login = SharedString::from(contact.user.github_login.clone()); let mut item = ListItem::new(github_login.clone()) - .on_click(cx.listener(move |this, _, cx| { - this.workspace - .update(cx, |this, cx| { - this.call_state() - .invite(user_id, None, cx) - .detach_and_log_err(cx) - }) - .log_err(); - })) + .on_click(cx.listener(move |this, _, cx| this.call(user_id, cx))) .child( h_stack() .w_full() diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index 3a0f0093bb..7e5354c601 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -99,37 +99,23 @@ impl Render for CollabTitlebarItem { type Element = Stateful
; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let is_in_room = self - .workspace - .update(cx, |this, cx| this.call_state().is_in_room(cx)) - .unwrap_or_default(); + let room = ActiveCall::global(cx).read(cx).room(); + let is_in_room = room.is_some(); let is_shared = is_in_room && self.project.read(cx).is_shared(); let current_user = self.user_store.read(cx).current_user(); let client = self.client.clone(); - let users = self - .workspace - .update(cx, |this, cx| this.call_state().remote_participants(cx)) - .log_err() - .flatten(); - let is_muted = self - .workspace - .update(cx, |this, cx| this.call_state().is_muted(cx)) - .log_err() - .flatten() - .unwrap_or_default(); - let is_deafened = self - .workspace - .update(cx, |this, cx| this.call_state().is_deafened(cx)) - .log_err() - .flatten() - .unwrap_or_default(); - let speakers_icon = if self - .workspace - .update(cx, |this, cx| this.call_state().is_deafened(cx)) - .log_err() - .flatten() - .unwrap_or_default() - { + let remote_participants = room.map(|room| { + room.read(cx) + .remote_participants() + .values() + .map(|participant| (participant.user.clone(), participant.peer_id)) + .collect::>() + }); + let is_muted = room.map_or(false, |room| room.read(cx).is_muted(cx)); + let is_deafened = room + .and_then(|room| room.read(cx).is_deafened()) + .unwrap_or(false); + let speakers_icon = if is_deafened { ui::Icon::AudioOff } else { ui::Icon::AudioOn @@ -165,7 +151,7 @@ impl Render for CollabTitlebarItem { .children(self.render_project_branch(cx)), ) .when_some( - users.zip(current_user.clone()), + remote_participants.zip(current_user.clone()), |this, (remote_participants, current_user)| { let mut pile = FacePile::default(); pile.extend( @@ -176,25 +162,30 @@ impl Render for CollabTitlebarItem { div().child(Avatar::data(avatar.clone())).into_any_element() }) .into_iter() - .chain(remote_participants.into_iter().flat_map(|(user, peer_id)| { - user.avatar.as_ref().map(|avatar| { - div() - .child( - Avatar::data(avatar.clone()).into_element().into_any(), - ) - .on_mouse_down(MouseButton::Left, { - let workspace = workspace.clone(); - move |_, cx| { - workspace - .update(cx, |this, cx| { - this.open_shared_screen(peer_id, cx); - }) - .log_err(); - } - }) - .into_any_element() - }) - })), + .chain(remote_participants.into_iter().filter_map( + |(user, peer_id)| { + let avatar = user.avatar.as_ref()?; + Some( + div() + .child( + Avatar::data(avatar.clone()) + .into_element() + .into_any(), + ) + .on_mouse_down(MouseButton::Left, { + let workspace = workspace.clone(); + move |_, cx| { + workspace + .update(cx, |this, cx| { + this.open_shared_screen(peer_id, cx); + }) + .log_err(); + } + }) + .into_any_element(), + ) + }, + )), ); this.child(pile.render(cx)) }, @@ -226,15 +217,10 @@ impl Render for CollabTitlebarItem { .child( IconButton::new("leave-call", ui::Icon::Exit) .style(ButtonStyle::Subtle) - .on_click({ - let workspace = workspace.clone(); - move |_, cx| { - workspace - .update(cx, |this, cx| { - this.call_state().hang_up(cx).detach(); - }) - .log_err(); - } + .on_click(move |_, cx| { + ActiveCall::global(cx) + .update(cx, |call, cx| call.hang_up(cx)) + .detach_and_log_err(cx); }), ), ) @@ -252,15 +238,8 @@ impl Render for CollabTitlebarItem { ) .style(ButtonStyle::Subtle) .selected(is_muted) - .on_click({ - let workspace = workspace.clone(); - move |_, cx| { - workspace - .update(cx, |this, cx| { - this.call_state().toggle_mute(cx); - }) - .log_err(); - } + .on_click(move |_, cx| { + crate::toggle_mute(&Default::default(), cx) }), ) .child( @@ -275,26 +254,15 @@ impl Render for CollabTitlebarItem { cx, ) }) - .on_click({ - let workspace = workspace.clone(); - move |_, cx| { - workspace - .update(cx, |this, cx| { - this.call_state().toggle_deafen(cx); - }) - .log_err(); - } + .on_click(move |_, cx| { + crate::toggle_mute(&Default::default(), cx) }), ) .child( IconButton::new("screen-share", ui::Icon::Screen) .style(ButtonStyle::Subtle) .on_click(move |_, cx| { - workspace - .update(cx, |this, cx| { - this.call_state().toggle_screen_share(cx); - }) - .log_err(); + crate::toggle_screen_sharing(&Default::default(), cx) }), ) .pl_2(), diff --git a/crates/collab_ui2/src/collab_ui.rs b/crates/collab_ui2/src/collab_ui.rs index 57a33c6790..efd3ff8692 100644 --- a/crates/collab_ui2/src/collab_ui.rs +++ b/crates/collab_ui2/src/collab_ui.rs @@ -9,22 +9,21 @@ mod panel_settings; use std::{rc::Rc, sync::Arc}; +use call::{report_call_event_for_room, ActiveCall, Room}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; use gpui::{ - point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, WindowBounds, WindowKind, - WindowOptions, + actions, point, AppContext, GlobalPixels, Pixels, PlatformDisplay, Size, Task, WindowBounds, + WindowKind, WindowOptions, }; pub use panel_settings::{ ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings, }; use settings::Settings; +use util::ResultExt; use workspace::AppState; -// actions!( -// collab, -// [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall] -// ); +actions!(ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall); pub fn init(app_state: &Arc, cx: &mut AppContext) { CollaborationPanelSettings::register(cx); @@ -42,61 +41,61 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { // cx.add_global_action(toggle_deafen); } -// pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { -// let call = ActiveCall::global(cx).read(cx); -// if let Some(room) = call.room().cloned() { -// let client = call.client(); -// let toggle_screen_sharing = room.update(cx, |room, cx| { -// if room.is_screen_sharing() { -// report_call_event_for_room( -// "disable screen share", -// room.id(), -// room.channel_id(), -// &client, -// cx, -// ); -// Task::ready(room.unshare_screen(cx)) -// } else { -// report_call_event_for_room( -// "enable screen share", -// room.id(), -// room.channel_id(), -// &client, -// cx, -// ); -// room.share_screen(cx) -// } -// }); -// toggle_screen_sharing.detach_and_log_err(cx); -// } -// } +pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) { + let call = ActiveCall::global(cx).read(cx); + if let Some(room) = call.room().cloned() { + let client = call.client(); + let toggle_screen_sharing = room.update(cx, |room, cx| { + if room.is_screen_sharing() { + report_call_event_for_room( + "disable screen share", + room.id(), + room.channel_id(), + &client, + cx, + ); + Task::ready(room.unshare_screen(cx)) + } else { + report_call_event_for_room( + "enable screen share", + room.id(), + room.channel_id(), + &client, + cx, + ); + room.share_screen(cx) + } + }); + toggle_screen_sharing.detach_and_log_err(cx); + } +} -// pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { -// let call = ActiveCall::global(cx).read(cx); -// if let Some(room) = call.room().cloned() { -// let client = call.client(); -// room.update(cx, |room, cx| { -// let operation = if room.is_muted(cx) { -// "enable microphone" -// } else { -// "disable microphone" -// }; -// report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx); +pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) { + let call = ActiveCall::global(cx).read(cx); + if let Some(room) = call.room().cloned() { + let client = call.client(); + room.update(cx, |room, cx| { + let operation = if room.is_muted(cx) { + "enable microphone" + } else { + "disable microphone" + }; + report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx); -// room.toggle_mute(cx) -// }) -// .map(|task| task.detach_and_log_err(cx)) -// .log_err(); -// } -// } + room.toggle_mute(cx) + }) + .map(|task| task.detach_and_log_err(cx)) + .log_err(); + } +} -// pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) { -// if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { -// room.update(cx, Room::toggle_deafen) -// .map(|task| task.detach_and_log_err(cx)) -// .log_err(); -// } -// } +pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) { + if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() { + room.update(cx, Room::toggle_deafen) + .map(|task| task.detach_and_log_err(cx)) + .log_err(); + } +} fn notification_window_options( screen: Rc, diff --git a/crates/workspace2/Cargo.toml b/crates/workspace2/Cargo.toml index 2dd7c6468e..a06ac6e3e0 100644 --- a/crates/workspace2/Cargo.toml +++ b/crates/workspace2/Cargo.toml @@ -20,6 +20,7 @@ test-support = [ [dependencies] db = { path = "../db2", package = "db2" } +call = { path = "../call2", package = "call2" } client = { path = "../client2", package = "client2" } collections = { path = "../collections" } # context_menu = { path = "../context_menu" } @@ -36,7 +37,6 @@ theme = { path = "../theme2", package = "theme2" } util = { path = "../util" } ui = { package = "ui2", path = "../ui2" } -async-trait.workspace = true async-recursion = "1.0.0" itertools = "0.10" bincode = "1.2.1" diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index c98fac00c6..4d5d582e13 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -1,5 +1,6 @@ use crate::{AppState, FollowerState, Pane, Workspace}; use anyhow::{anyhow, bail, Result}; +use call::ActiveCall; use collections::HashMap; use db::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, @@ -126,6 +127,7 @@ impl PaneGroup { &self, project: &Model, follower_states: &HashMap, FollowerState>, + active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, @@ -135,6 +137,7 @@ impl PaneGroup { project, 0, follower_states, + active_call, active_pane, zoomed, app_state, @@ -196,6 +199,7 @@ impl Member { project: &Model, basis: usize, follower_states: &HashMap, FollowerState>, + active_call: Option<&Model>, active_pane: &View, zoomed: Option<&AnyWeakView>, app_state: &Arc, diff --git a/crates/call2/src/shared_screen.rs b/crates/workspace2/src/shared_screen.rs similarity index 94% rename from crates/call2/src/shared_screen.rs rename to crates/workspace2/src/shared_screen.rs index c38ebeac02..c4bcb31958 100644 --- a/crates/call2/src/shared_screen.rs +++ b/crates/workspace2/src/shared_screen.rs @@ -1,5 +1,9 @@ -use crate::participant::{Frame, RemoteVideoTrack}; +use crate::{ + item::{Item, ItemEvent}, + ItemNavHistory, WorkspaceId, +}; use anyhow::Result; +use call::participant::{Frame, RemoteVideoTrack}; use client::{proto::PeerId, User}; use futures::StreamExt; use gpui::{ @@ -9,7 +13,6 @@ use gpui::{ }; use std::sync::{Arc, Weak}; use ui::{h_stack, Icon, IconElement}; -use workspace::{item::Item, ItemNavHistory, WorkspaceId}; pub enum Event { Close, @@ -56,7 +59,7 @@ impl SharedScreen { } impl EventEmitter for SharedScreen {} -impl EventEmitter for SharedScreen {} +impl EventEmitter for SharedScreen {} impl FocusableView for SharedScreen { fn focus_handle(&self, _: &AppContext) -> FocusHandle { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 66eea70670..ea796274bb 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -10,15 +10,16 @@ mod persistence; pub mod searchable; // todo!() mod modal_layer; +mod shared_screen; mod status_bar; mod toolbar; mod workspace_settings; use anyhow::{anyhow, Context as _, Result}; -use async_trait::async_trait; +use call::ActiveCall; use client::{ proto::{self, PeerId}, - Client, TypedEnvelope, User, UserStore, + Client, Status, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; @@ -28,11 +29,11 @@ use futures::{ Future, FutureExt, StreamExt, }; use gpui::{ - actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext, - AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, - FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext, - ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task, - View, ViewContext, VisualContext, WeakModel, WeakView, WindowBounds, WindowContext, + actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AnyWindowHandle, AppContext, + AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, + FocusHandle, FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, + ModelContext, ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, + Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; @@ -52,6 +53,7 @@ use postage::stream::Stream; use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; use serde::Deserialize; use settings::Settings; +use shared_screen::SharedScreen; use status_bar::StatusBar; pub use status_bar::StatusItemView; use std::{ @@ -209,6 +211,7 @@ pub fn init_settings(cx: &mut AppContext) { pub fn init(app_state: Arc, cx: &mut AppContext) { init_settings(cx); notifications::init(cx); + // cx.add_global_action({ // let app_state = Arc::downgrade(&app_state); // move |_: &Open, cx: &mut AppContext| { @@ -302,7 +305,6 @@ pub struct AppState { pub user_store: Model, pub workspace_store: Model, pub fs: Arc, - pub call_factory: CallFactory, pub build_window_options: fn(Option, Option, &mut AppContext) -> WindowOptions, pub node_runtime: Arc, @@ -321,69 +323,6 @@ struct Follower { peer_id: PeerId, } -#[cfg(any(test, feature = "test-support"))] -pub struct TestCallHandler; - -#[cfg(any(test, feature = "test-support"))] -impl CallHandler for TestCallHandler { - fn peer_state( - &mut self, - id: PeerId, - project: &Model, - cx: &mut ViewContext, - ) -> Option<(bool, bool)> { - None - } - - fn shared_screen_for_peer( - &self, - peer_id: PeerId, - pane: &View, - cx: &mut ViewContext, - ) -> Option> { - None - } - - fn room_id(&self, cx: &AppContext) -> Option { - None - } - - fn hang_up(&self, cx: &mut AppContext) -> Task> { - Task::ready(Err(anyhow!("TestCallHandler should not be hanging up"))) - } - - fn active_project(&self, cx: &AppContext) -> Option> { - None - } - - fn invite( - &mut self, - called_user_id: u64, - initial_project: Option>, - cx: &mut AppContext, - ) -> Task> { - unimplemented!() - } - - fn remote_participants(&self, cx: &AppContext) -> Option, PeerId)>> { - None - } - - fn is_muted(&self, cx: &AppContext) -> Option { - None - } - - fn toggle_mute(&self, cx: &mut AppContext) {} - - fn toggle_screen_share(&self, cx: &mut AppContext) {} - - fn toggle_deafen(&self, cx: &mut AppContext) {} - - fn is_deafened(&self, cx: &AppContext) -> Option { - None - } -} - impl AppState { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Arc { @@ -414,7 +353,6 @@ impl AppState { workspace_store, node_runtime: FakeNodeRuntime::new(), build_window_options: |_, _, _| Default::default(), - call_factory: |_| Box::new(TestCallHandler), }) } } @@ -471,40 +409,6 @@ pub enum Event { WorkspaceCreated(WeakView), } -#[async_trait(?Send)] -pub trait CallHandler { - fn peer_state( - &mut self, - id: PeerId, - project: &Model, - cx: &mut ViewContext, - ) -> Option<(bool, bool)>; - fn shared_screen_for_peer( - &self, - peer_id: PeerId, - pane: &View, - cx: &mut ViewContext, - ) -> Option>; - fn room_id(&self, cx: &AppContext) -> Option; - fn is_in_room(&self, cx: &mut ViewContext) -> bool { - self.room_id(cx).is_some() - } - fn hang_up(&self, cx: &mut AppContext) -> Task>; - fn active_project(&self, cx: &AppContext) -> Option>; - fn invite( - &mut self, - called_user_id: u64, - initial_project: Option>, - cx: &mut AppContext, - ) -> Task>; - fn remote_participants(&self, cx: &AppContext) -> Option, PeerId)>>; - fn is_muted(&self, cx: &AppContext) -> Option; - fn is_deafened(&self, cx: &AppContext) -> Option; - fn toggle_mute(&self, cx: &mut AppContext); - fn toggle_deafen(&self, cx: &mut AppContext); - fn toggle_screen_share(&self, cx: &mut AppContext); -} - pub struct Workspace { window_self: WindowHandle, weak_self: WeakView, @@ -525,10 +429,10 @@ pub struct Workspace { titlebar_item: Option, notifications: Vec<(TypeId, usize, Box)>, project: Model, - call_handler: Box, follower_states: HashMap, FollowerState>, last_leaders_by_pane: HashMap, PeerId>, window_edited: bool, + active_call: Option<(Model, Vec)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, app_state: Arc, @@ -556,7 +460,6 @@ struct FollowerState { enum WorkspaceBounds {} -type CallFactory = fn(&mut ViewContext) -> Box; impl Workspace { pub fn new( workspace_id: WorkspaceId, @@ -648,19 +551,9 @@ impl Workspace { mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>(); let _apply_leader_updates = cx.spawn(|this, mut cx| async move { while let Some((leader_id, update)) = leader_updates_rx.next().await { - let mut cx2 = cx.clone(); - let t = this.clone(); - - Workspace::process_leader_update(&this, leader_id, update, &mut cx) + Self::process_leader_update(&this, leader_id, update, &mut cx) .await .log_err(); - - // this.update(&mut cx, |this, cxx| { - // this.call_handler - // .process_leader_update(leader_id, update, cx2) - // })? - // .await - // .log_err(); } Ok(()) @@ -693,6 +586,14 @@ impl Workspace { // drag_and_drop.register_container(weak_handle.clone()); // }); + let mut active_call = None; + if cx.has_global::>() { + let call = cx.global::>().clone(); + let mut subscriptions = Vec::new(); + subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); + active_call = Some((call, subscriptions)); + } + let subscriptions = vec![ cx.observe_window_activation(Self::on_window_activation_changed), cx.observe_window_bounds(move |_, cx| { @@ -769,8 +670,7 @@ impl Workspace { follower_states: Default::default(), last_leaders_by_pane: Default::default(), window_edited: false, - - call_handler: (app_state.call_factory)(cx), + active_call, database_id: workspace_id, app_state, _observe_current_user, @@ -1217,7 +1117,7 @@ impl Workspace { cx: &mut ViewContext, ) -> Task> { //todo!(saveing) - + let active_call = self.active_call().cloned(); let window = cx.window_handle(); cx.spawn(|this, mut cx| async move { @@ -1228,27 +1128,27 @@ impl Workspace { .count() })?; - if !quitting - && workspace_count == 1 - && this - .update(&mut cx, |this, cx| this.call_handler.is_in_room(cx)) - .log_err() - .unwrap_or_default() - { - let answer = window.update(&mut cx, |_, cx| { - cx.prompt( - PromptLevel::Warning, - "Do you want to leave the current call?", - &["Close window and hang up", "Cancel"], - ) - })?; + if let Some(active_call) = active_call { + if !quitting + && workspace_count == 1 + && active_call.read_with(&cx, |call, _| call.room().is_some())? + { + let answer = window.update(&mut cx, |_, cx| { + cx.prompt( + PromptLevel::Warning, + "Do you want to leave the current call?", + &["Close window and hang up", "Cancel"], + ) + })?; - if answer.await.log_err() == Some(1) { - return anyhow::Ok(false); - } else { - this.update(&mut cx, |this, cx| this.call_handler.hang_up(cx))? - .await - .log_err(); + if answer.await.log_err() == Some(1) { + return anyhow::Ok(false); + } else { + active_call + .update(&mut cx, |call, cx| call.hang_up(cx))? + .await + .log_err(); + } } } @@ -2032,7 +1932,7 @@ impl Workspace { pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext) { if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) { self.active_pane.update(cx, |pane, cx| { - pane.add_item(shared_screen, false, true, None, cx) + pane.add_item(Box::new(shared_screen), false, true, None, cx) }); } } @@ -2510,19 +2410,19 @@ impl Workspace { // } pub fn unfollow(&mut self, pane: &View, cx: &mut ViewContext) -> Option { - let follower_states = &mut self.follower_states; - let state = follower_states.remove(pane)?; + let state = self.follower_states.remove(pane)?; let leader_id = state.leader_id; for (_, item) in state.items_by_leader_view_id { item.set_leader_peer_id(None, cx); } - if follower_states + if self + .follower_states .values() .all(|state| state.leader_id != state.leader_id) { let project_id = self.project.read(cx).remote_id(); - let room_id = self.call_handler.room_id(cx)?; + let room_id = self.active_call()?.read(cx).room()?.read(cx).id(); self.app_state .client .send(proto::Unfollow { @@ -2878,9 +2778,8 @@ impl Workspace { } else { None }; - let room_id = self.call_handler.room_id(cx)?; self.app_state().workspace_store.update(cx, |store, cx| { - store.update_followers(project_id, room_id, update, cx) + store.update_followers(project_id, update, cx) }) } @@ -2888,12 +2787,31 @@ impl Workspace { self.follower_states.get(pane).map(|state| state.leader_id) } - pub fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { + fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext) -> Option<()> { cx.notify(); - let (leader_in_this_project, leader_in_this_app) = - self.call_handler.peer_state(leader_id, &self.project, cx)?; + let call = self.active_call()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(leader_id)?; let mut items_to_activate = Vec::new(); + + let leader_in_this_app; + let leader_in_this_project; + match participant.location { + call::ParticipantLocation::SharedProject { project_id } => { + leader_in_this_app = true; + leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id(); + } + call::ParticipantLocation::UnsharedProject => { + leader_in_this_app = true; + leader_in_this_project = false; + } + call::ParticipantLocation::External => { + leader_in_this_app = false; + leader_in_this_project = false; + } + }; + for (pane, state) in &self.follower_states { if state.leader_id != leader_id { continue; @@ -2914,7 +2832,7 @@ impl Workspace { } if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - items_to_activate.push((pane.clone(), shared_screen)); + items_to_activate.push((pane.clone(), Box::new(shared_screen))); } } @@ -2923,8 +2841,8 @@ impl Workspace { if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) { pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx)); } else { - pane.update(cx, |pane, mut cx| { - pane.add_item(item.boxed_clone(), false, false, None, &mut cx) + pane.update(cx, |pane, cx| { + pane.add_item(item.boxed_clone(), false, false, None, cx) }); } @@ -2941,21 +2859,20 @@ impl Workspace { peer_id: PeerId, pane: &View, cx: &mut ViewContext, - ) -> Option> { - self.call_handler.shared_screen_for_peer(peer_id, pane, cx) - // let call = self.active_call()?; - // let room = call.read(cx).room()?.read(cx); - // let participant = room.remote_participant_for_peer_id(peer_id)?; - // let track = participant.video_tracks.values().next()?.clone(); - // let user = participant.user.clone(); + ) -> Option> { + let call = self.active_call()?; + let room = call.read(cx).room()?.read(cx); + let participant = room.remote_participant_for_peer_id(peer_id)?; + let track = participant.video_tracks.values().next()?.clone(); + let user = participant.user.clone(); - // for item in pane.read(cx).items_of_type::() { - // if item.read(cx).peer_id == peer_id { - // return Some(item); - // } - // } + for item in pane.read(cx).items_of_type::() { + if item.read(cx).peer_id == peer_id { + return Some(item); + } + } - // Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) + Some(cx.build_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) } pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext) { @@ -2984,6 +2901,25 @@ impl Workspace { } } + fn active_call(&self) -> Option<&Model> { + self.active_call.as_ref().map(|(call, _)| call) + } + + fn on_active_call_event( + &mut self, + _: Model, + event: &call::room::Event, + cx: &mut ViewContext, + ) { + match event { + call::room::Event::ParticipantLocationChanged { participant_id } + | call::room::Event::RemoteVideoTracksChanged { participant_id } => { + self.leader_updated(*participant_id, cx); + } + _ => {} + } + } + pub fn database_id(&self) -> WorkspaceId { self.database_id } @@ -3393,7 +3329,6 @@ impl Workspace { fs: project.read(cx).fs().clone(), build_window_options: |_, _, _| Default::default(), node_runtime: FakeNodeRuntime::new(), - call_factory: |_| Box::new(TestCallHandler), }); let workspace = Self::new(0, project, app_state, cx); workspace.active_pane.update(cx, |pane, cx| pane.focus(cx)); @@ -3472,10 +3407,6 @@ impl Workspace { self.modal_layer .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build)) } - - pub fn call_state(&mut self) -> &mut dyn CallHandler { - &mut *self.call_handler - } } fn window_bounds_env_override(cx: &AsyncAppContext) -> Option { @@ -3676,6 +3607,7 @@ impl Render for Workspace { .child(self.center.render( &self.project, &self.follower_states, + self.active_call(), &self.active_pane, self.zoomed.as_ref(), &self.app_state, @@ -3846,10 +3778,14 @@ impl WorkspaceStore { pub fn update_followers( &self, project_id: Option, - room_id: u64, update: proto::update_followers::Variant, cx: &AppContext, ) -> Option<()> { + if !cx.has_global::>() { + return None; + } + + let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id(); let follower_ids: Vec<_> = self .followers .iter() @@ -3885,17 +3821,9 @@ impl WorkspaceStore { project_id: envelope.payload.project_id, peer_id: envelope.original_sender_id()?, }; + let active_project = ActiveCall::global(cx).read(cx).location().cloned(); + let mut response = proto::FollowResponse::default(); - let active_project = this - .workspaces - .iter() - .next() - .and_then(|workspace| { - workspace - .read_with(cx, |this, cx| this.call_handler.active_project(cx)) - .log_err() - }) - .flatten(); for workspace in &this.workspaces { workspace .update(cx, |workspace, cx| { @@ -4048,187 +3976,184 @@ pub async fn last_opened_workspace_paths() -> Option { DB.last_workspace().await.log_err().flatten() } -// async fn join_channel_internal( -// channel_id: u64, -// app_state: &Arc, -// requesting_window: Option>, -// active_call: &ModelHandle, -// cx: &mut AsyncAppContext, -// ) -> Result { -// let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| { -// let Some(room) = active_call.room().map(|room| room.read(cx)) else { -// return (false, None); -// }; +async fn join_channel_internal( + channel_id: u64, + app_state: &Arc, + requesting_window: Option>, + active_call: &Model, + cx: &mut AsyncAppContext, +) -> Result { + let (should_prompt, open_room) = active_call.read_with(cx, |active_call, cx| { + let Some(room) = active_call.room().map(|room| room.read(cx)) else { + return (false, None); + }; -// let already_in_channel = room.channel_id() == Some(channel_id); -// let should_prompt = room.is_sharing_project() -// && room.remote_participants().len() > 0 -// && !already_in_channel; -// let open_room = if already_in_channel { -// active_call.room().cloned() -// } else { -// None -// }; -// (should_prompt, open_room) -// }); + let already_in_channel = room.channel_id() == Some(channel_id); + let should_prompt = room.is_sharing_project() + && room.remote_participants().len() > 0 + && !already_in_channel; + let open_room = if already_in_channel { + active_call.room().cloned() + } else { + None + }; + (should_prompt, open_room) + })?; -// if let Some(room) = open_room { -// let task = room.update(cx, |room, cx| { -// if let Some((project, host)) = room.most_active_project(cx) { -// return Some(join_remote_project(project, host, app_state.clone(), cx)); -// } + if let Some(room) = open_room { + let task = room.update(cx, |room, cx| { + if let Some((project, host)) = room.most_active_project(cx) { + return Some(join_remote_project(project, host, app_state.clone(), cx)); + } -// None -// }); -// if let Some(task) = task { -// task.await?; -// } -// return anyhow::Ok(true); -// } + None + })?; + if let Some(task) = task { + task.await?; + } + return anyhow::Ok(true); + } -// if should_prompt { -// if let Some(workspace) = requesting_window { -// if let Some(window) = workspace.update(cx, |cx| cx.window()) { -// let answer = window.prompt( -// PromptLevel::Warning, -// "Leaving this call will unshare your current project.\nDo you want to switch channels?", -// &["Yes, Join Channel", "Cancel"], -// cx, -// ); + if should_prompt { + if let Some(workspace) = requesting_window { + let answer = workspace.update(cx, |_, cx| { + cx.prompt( + PromptLevel::Warning, + "Leaving this call will unshare your current project.\nDo you want to switch channels?", + &["Yes, Join Channel", "Cancel"], + ) + })?.await; -// if let Some(mut answer) = answer { -// if answer.next().await == Some(1) { -// return Ok(false); -// } -// } -// } else { -// return Ok(false); // unreachable!() hopefully -// } -// } else { -// return Ok(false); // unreachable!() hopefully -// } -// } + if answer == Ok(1) { + return Ok(false); + } + } else { + return Ok(false); // unreachable!() hopefully + } + } -// let client = cx.read(|cx| active_call.read(cx).client()); + let client = cx.update(|cx| active_call.read(cx).client())?; -// let mut client_status = client.status(); + let mut client_status = client.status(); -// // this loop will terminate within client::CONNECTION_TIMEOUT seconds. -// 'outer: loop { -// let Some(status) = client_status.recv().await else { -// return Err(anyhow!("error connecting")); -// }; + // this loop will terminate within client::CONNECTION_TIMEOUT seconds. + 'outer: loop { + let Some(status) = client_status.recv().await else { + return Err(anyhow!("error connecting")); + }; -// match status { -// Status::Connecting -// | Status::Authenticating -// | Status::Reconnecting -// | Status::Reauthenticating => continue, -// Status::Connected { .. } => break 'outer, -// Status::SignedOut => return Err(anyhow!("not signed in")), -// Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), -// Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { -// return Err(anyhow!("zed is offline")) -// } -// } -// } + match status { + Status::Connecting + | Status::Authenticating + | Status::Reconnecting + | Status::Reauthenticating => continue, + Status::Connected { .. } => break 'outer, + Status::SignedOut => return Err(anyhow!("not signed in")), + Status::UpgradeRequired => return Err(anyhow!("zed is out of date")), + Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => { + return Err(anyhow!("zed is offline")) + } + } + } -// let room = active_call -// .update(cx, |active_call, cx| { -// active_call.join_channel(channel_id, cx) -// }) -// .await?; + let room = active_call + .update(cx, |active_call, cx| { + active_call.join_channel(channel_id, cx) + })? + .await?; -// room.update(cx, |room, _| room.room_update_completed()) -// .await; + let Some(room) = room else { + return anyhow::Ok(true); + }; -// let task = room.update(cx, |room, cx| { -// if let Some((project, host)) = room.most_active_project(cx) { -// return Some(join_remote_project(project, host, app_state.clone(), cx)); -// } + room.update(cx, |room, _| room.room_update_completed())? + .await; -// None -// }); -// if let Some(task) = task { -// task.await?; -// return anyhow::Ok(true); -// } -// anyhow::Ok(false) -// } + let task = room.update(cx, |room, cx| { + if let Some((project, host)) = room.most_active_project(cx) { + return Some(join_remote_project(project, host, app_state.clone(), cx)); + } -// pub fn join_channel( -// channel_id: u64, -// app_state: Arc, -// requesting_window: Option>, -// cx: &mut AppContext, -// ) -> Task> { -// let active_call = ActiveCall::global(cx); -// cx.spawn(|mut cx| async move { -// let result = join_channel_internal( -// channel_id, -// &app_state, -// requesting_window, -// &active_call, -// &mut cx, -// ) -// .await; + None + })?; + if let Some(task) = task { + task.await?; + return anyhow::Ok(true); + } + anyhow::Ok(false) +} -// // join channel succeeded, and opened a window -// if matches!(result, Ok(true)) { -// return anyhow::Ok(()); -// } +pub fn join_channel( + channel_id: u64, + app_state: Arc, + requesting_window: Option>, + cx: &mut AppContext, +) -> Task> { + let active_call = ActiveCall::global(cx); + cx.spawn(|mut cx| async move { + let result = join_channel_internal( + channel_id, + &app_state, + requesting_window, + &active_call, + &mut cx, + ) + .await; -// if requesting_window.is_some() { -// return anyhow::Ok(()); -// } + // join channel succeeded, and opened a window + if matches!(result, Ok(true)) { + return anyhow::Ok(()); + } -// // find an existing workspace to focus and show call controls -// let mut active_window = activate_any_workspace_window(&mut cx); -// if active_window.is_none() { -// // no open workspaces, make one to show the error in (blergh) -// cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx)) -// .await; -// } + if requesting_window.is_some() { + return anyhow::Ok(()); + } -// active_window = activate_any_workspace_window(&mut cx); -// if active_window.is_none() { -// return result.map(|_| ()); // unreachable!() assuming new_local always opens a window -// } + // find an existing workspace to focus and show call controls + let mut active_window = activate_any_workspace_window(&mut cx); + if active_window.is_none() { + // no open workspaces, make one to show the error in (blergh) + cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))? + .await?; + } -// if let Err(err) = result { -// let prompt = active_window.unwrap().prompt( -// PromptLevel::Critical, -// &format!("Failed to join channel: {}", err), -// &["Ok"], -// &mut cx, -// ); -// if let Some(mut prompt) = prompt { -// prompt.next().await; -// } else { -// return Err(err); -// } -// } + active_window = activate_any_workspace_window(&mut cx); + let Some(active_window) = active_window else { + return anyhow::Ok(()); + }; -// // return ok, we showed the error to the user. -// return anyhow::Ok(()); -// }) -// } + if let Err(err) = result { + active_window + .update(&mut cx, |_, cx| { + cx.prompt( + PromptLevel::Critical, + &format!("Failed to join channel: {}", err), + &["Ok"], + ) + })? + .await + .ok(); + } -// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option { -// for window in cx.windows() { -// let found = window.update(cx, |cx| { -// let is_workspace = cx.root_view().clone().downcast::().is_some(); -// if is_workspace { -// cx.activate_window(); -// } -// is_workspace -// }); -// if found == Some(true) { -// return Some(window); -// } -// } -// None -// } + // return ok, we showed the error to the user. + return anyhow::Ok(()); + }) +} + +pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option { + cx.update(|cx| { + for window in cx.windows() { + let is_workspace = window.downcast::().is_some(); + if is_workspace { + window.update(cx, |_, cx| cx.activate_window()).ok(); + return Some(window); + } + } + None + }) + .ok() + .flatten() +} #[allow(clippy::type_complexity)] pub fn open_paths( diff --git a/crates/zed2/src/main.rs b/crates/zed2/src/main.rs index 4c7e914e37..5b641acfa0 100644 --- a/crates/zed2/src/main.rs +++ b/crates/zed2/src/main.rs @@ -191,7 +191,6 @@ fn main() { user_store: user_store.clone(), fs, build_window_options, - call_factory: call::Call::new, workspace_store, node_runtime, });