Revert "Decouple workspace from call (#3380)"

Max Brunsfeld created

This reverts commit 6da57cbc6e33a7d1ede533ecc11948292feb22f3, reversing
changes made to 62b18437044c9e2b7e4ef2ba24fbbf12444a48c7.

Also, adjust new code that was written using the "call handler".

Change summary

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 
crates/workspace2/src/shared_screen.rs        |   9 
crates/workspace2/src/workspace2.rs           | 647 +++++++++-----------
crates/zed2/src/main.rs                       |   1 
15 files changed, 440 insertions(+), 814 deletions(-)

Detailed changes

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",

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

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<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
     CallSettings::register(cx);
@@ -334,55 +327,12 @@ impl ActiveCall {
     pub fn join_channel(
         &mut self,
         channel_id: u64,
-        requesting_window: Option<WindowHandle<Workspace>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Option<Model<Room>>>> {
         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<ActiveCall>, Vec<Subscription>)>,
-}
-
-impl Call {
-    pub fn new(cx: &mut ViewContext<'_, Workspace>) -> Box<dyn CallHandler> {
-        let mut active_call = None;
-        if cx.has_global::<Model<ActiveCall>>() {
-            let call = cx.global::<Model<ActiveCall>>().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<ActiveCall>,
-        event: &room::Event,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        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<Project>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> 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<Pane>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Option<Box<dyn ItemHandle>> {
-        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::<SharedScreen>() {
-            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<u64> {
-        Some(self.active_call.as_ref()?.0.read(cx).room()?.read(cx).id())
-    }
-    fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>> {
-        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<WeakModel<Project>> {
-        ActiveCall::global(cx).read(cx).location().cloned()
-    }
-    fn invite(
-        &mut self,
-        called_user_id: u64,
-        initial_project: Option<Model<Project>>,
-        cx: &mut AppContext,
-    ) -> Task<Result<()>> {
-        ActiveCall::global(cx).update(cx, |this, cx| {
-            this.invite(called_user_id, initial_project, cx)
-        })
-    }
-    fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, 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<bool> {
-        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<bool> {
-        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;

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;
 

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();
 

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)

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| {

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<Self>) {
+        let Some(workspace) = self.workspace.upgrade() else {
+            return;
+        };
         let Some(handle) = cx.window_handle().downcast::<Workspace>() 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<Self>) {
@@ -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()

crates/collab_ui2/src/collab_titlebar_item.rs 🔗

@@ -99,37 +99,23 @@ impl Render for CollabTitlebarItem {
     type Element = Stateful<Div>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> 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::<Vec<_>>()
+        });
+        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(),

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<AppState>, cx: &mut AppContext) {
     CollaborationPanelSettings::register(cx);
@@ -42,61 +41,61 @@ pub fn init(app_state: &Arc<AppState>, 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<dyn PlatformDisplay>,

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"

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<Project>,
         follower_states: &HashMap<View<Pane>, FollowerState>,
+        active_call: Option<&Model<ActiveCall>>,
         active_pane: &View<Pane>,
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
@@ -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<Project>,
         basis: usize,
         follower_states: &HashMap<View<Pane>, FollowerState>,
+        active_call: Option<&Model<ActiveCall>>,
         active_pane: &View<Pane>,
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,

crates/call2/src/shared_screen.rs → 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<Event> for SharedScreen {}
-impl EventEmitter<workspace::item::ItemEvent> for SharedScreen {}
+impl EventEmitter<ItemEvent> for SharedScreen {}
 
 impl FocusableView for SharedScreen {
     fn focus_handle(&self, _: &AppContext) -> FocusHandle {

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<AppState>, 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<UserStore>,
     pub workspace_store: Model<WorkspaceStore>,
     pub fs: Arc<dyn fs::Fs>,
-    pub call_factory: CallFactory,
     pub build_window_options:
         fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
     pub node_runtime: Arc<dyn NodeRuntime>,
@@ -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<Project>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Option<(bool, bool)> {
-        None
-    }
-
-    fn shared_screen_for_peer(
-        &self,
-        peer_id: PeerId,
-        pane: &View<Pane>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Option<Box<dyn ItemHandle>> {
-        None
-    }
-
-    fn room_id(&self, cx: &AppContext) -> Option<u64> {
-        None
-    }
-
-    fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>> {
-        Task::ready(Err(anyhow!("TestCallHandler should not be hanging up")))
-    }
-
-    fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>> {
-        None
-    }
-
-    fn invite(
-        &mut self,
-        called_user_id: u64,
-        initial_project: Option<Model<Project>>,
-        cx: &mut AppContext,
-    ) -> Task<Result<()>> {
-        unimplemented!()
-    }
-
-    fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>> {
-        None
-    }
-
-    fn is_muted(&self, cx: &AppContext) -> Option<bool> {
-        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<bool> {
-        None
-    }
-}
-
 impl AppState {
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &mut AppContext) -> Arc<Self> {
@@ -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<Workspace>),
 }
 
-#[async_trait(?Send)]
-pub trait CallHandler {
-    fn peer_state(
-        &mut self,
-        id: PeerId,
-        project: &Model<Project>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Option<(bool, bool)>;
-    fn shared_screen_for_peer(
-        &self,
-        peer_id: PeerId,
-        pane: &View<Pane>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Option<Box<dyn ItemHandle>>;
-    fn room_id(&self, cx: &AppContext) -> Option<u64>;
-    fn is_in_room(&self, cx: &mut ViewContext<Workspace>) -> bool {
-        self.room_id(cx).is_some()
-    }
-    fn hang_up(&self, cx: &mut AppContext) -> Task<Result<()>>;
-    fn active_project(&self, cx: &AppContext) -> Option<WeakModel<Project>>;
-    fn invite(
-        &mut self,
-        called_user_id: u64,
-        initial_project: Option<Model<Project>>,
-        cx: &mut AppContext,
-    ) -> Task<Result<()>>;
-    fn remote_participants(&self, cx: &AppContext) -> Option<Vec<(Arc<User>, PeerId)>>;
-    fn is_muted(&self, cx: &AppContext) -> Option<bool>;
-    fn is_deafened(&self, cx: &AppContext) -> Option<bool>;
-    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<Self>,
     weak_self: WeakView<Self>,
@@ -525,10 +429,10 @@ pub struct Workspace {
     titlebar_item: Option<AnyView>,
     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
     project: Model<Project>,
-    call_handler: Box<dyn CallHandler>,
     follower_states: HashMap<View<Pane>, FollowerState>,
     last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
     window_edited: bool,
+    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
     database_id: WorkspaceId,
     app_state: Arc<AppState>,
@@ -556,7 +460,6 @@ struct FollowerState {
 
 enum WorkspaceBounds {}
 
-type CallFactory = fn(&mut ViewContext<Workspace>) -> Box<dyn CallHandler>;
 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::<Model<ActiveCall>>() {
+            let call = cx.global::<Model<ActiveCall>>().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<Self>,
     ) -> Task<Result<bool>> {
         //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<Self>) {
         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<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
-        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<Self>) -> Option<()> {
+    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> 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<Pane>,
         cx: &mut ViewContext<Self>,
-    ) -> Option<Box<dyn ItemHandle>> {
-        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();
-
-        // for item in pane.read(cx).items_of_type::<SharedScreen>() {
-        //     if item.read(cx).peer_id == peer_id {
-        //         return Some(item);
-        //     }
-        // }
+    ) -> Option<View<SharedScreen>> {
+        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::<SharedScreen>() {
+            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<Self>) {
@@ -2984,6 +2901,25 @@ impl Workspace {
         }
     }
 
+    fn active_call(&self) -> Option<&Model<ActiveCall>> {
+        self.active_call.as_ref().map(|(call, _)| call)
+    }
+
+    fn on_active_call_event(
+        &mut self,
+        _: Model<ActiveCall>,
+        event: &call::room::Event,
+        cx: &mut ViewContext<Self>,
+    ) {
+        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<WindowBounds> {
@@ -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<u64>,
-        room_id: u64,
         update: proto::update_followers::Variant,
         cx: &AppContext,
     ) -> Option<()> {
+        if !cx.has_global::<Model<ActiveCall>>() {
+            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<WorkspaceLocation> {
     DB.last_workspace().await.log_err().flatten()
 }
 
-// async fn join_channel_internal(
-//     channel_id: u64,
-//     app_state: &Arc<AppState>,
-//     requesting_window: Option<WindowHandle<Workspace>>,
-//     active_call: &ModelHandle<ActiveCall>,
-//     cx: &mut AsyncAppContext,
-// ) -> Result<bool> {
-//     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)
-//     });
-
-//     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);
-//     }
+async fn join_channel_internal(
+    channel_id: u64,
+    app_state: &Arc<AppState>,
+    requesting_window: Option<WindowHandle<Workspace>>,
+    active_call: &Model<ActiveCall>,
+    cx: &mut AsyncAppContext,
+) -> Result<bool> {
+    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);
+        };
 
-//     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 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
-//         }
-//     }
+        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 client = cx.read(|cx| active_call.read(cx).client());
-
-//     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"));
-//         };
-
-//         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"))
-//             }
-//         }
-//     }
+    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));
+            }
 
-//     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 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);
-//     }
-//     anyhow::Ok(false)
-// }
+            None
+        })?;
+        if let Some(task) = task {
+            task.await?;
+        }
+        return anyhow::Ok(true);
+    }
 
-// pub fn join_channel(
-//     channel_id: u64,
-//     app_state: Arc<AppState>,
-//     requesting_window: Option<WindowHandle<Workspace>>,
-//     cx: &mut AppContext,
-// ) -> Task<Result<()>> {
-//     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;
-
-//         // join channel succeeded, and opened a window
-//         if matches!(result, Ok(true)) {
-//             return anyhow::Ok(());
-//         }
-
-//         if requesting_window.is_some() {
-//             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;
-//         }
-
-//         active_window = activate_any_workspace_window(&mut cx);
-//         if active_window.is_none() {
-//             return result.map(|_| ()); // unreachable!() assuming new_local always opens a window
-//         }
-
-//         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);
-//             }
-//         }
-
-//         // return ok, we showed the error to the user.
-//         return anyhow::Ok(());
-//     })
-// }
+    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;
 
-// pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
-//     for window in cx.windows() {
-//         let found = window.update(cx, |cx| {
-//             let is_workspace = cx.root_view().clone().downcast::<Workspace>().is_some();
-//             if is_workspace {
-//                 cx.activate_window();
-//             }
-//             is_workspace
-//         });
-//         if found == Some(true) {
-//             return Some(window);
-//         }
-//     }
-//     None
-// }
+            if answer == Ok(1) {
+                return Ok(false);
+            }
+        } else {
+            return Ok(false); // unreachable!() hopefully
+        }
+    }
+
+    let client = cx.update(|cx| active_call.read(cx).client())?;
+
+    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"));
+        };
+
+        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 Some(room) = room else {
+        return anyhow::Ok(true);
+    };
+
+    room.update(cx, |room, _| room.room_update_completed())?
+        .await;
+
+    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);
+    }
+    anyhow::Ok(false)
+}
+
+pub fn join_channel(
+    channel_id: u64,
+    app_state: Arc<AppState>,
+    requesting_window: Option<WindowHandle<Workspace>>,
+    cx: &mut AppContext,
+) -> Task<Result<()>> {
+    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;
+
+        // join channel succeeded, and opened a window
+        if matches!(result, Ok(true)) {
+            return anyhow::Ok(());
+        }
+
+        if requesting_window.is_some() {
+            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?;
+        }
+
+        active_window = activate_any_workspace_window(&mut cx);
+        let Some(active_window) = active_window else {
+            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();
+        }
+
+        // return ok, we showed the error to the user.
+        return anyhow::Ok(());
+    })
+}
+
+pub fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
+    cx.update(|cx| {
+        for window in cx.windows() {
+            let is_workspace = window.downcast::<Workspace>().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(

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,
         });