diff --git a/Cargo.lock b/Cargo.lock index 693f2d9156e4d8ae683b77f445bb06242e2a36c1..27ff88f68a49f249a35291673a9420164db29c2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1193,6 +1193,7 @@ dependencies = [ "fs2", "futures 0.3.28", "gpui2", + "image", "language2", "live_kit_client2", "log", @@ -1204,6 +1205,7 @@ dependencies = [ "serde_derive", "serde_json", "settings2", + "smallvec", "util", "workspace2", ] diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index 43e19b4ccb4738c42654dffaf7797d75dc88c084..a258cbe2cf3edd10931022067627106d99890ac8 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -36,11 +36,13 @@ async-trait.workspace = true anyhow.workspace = true async-broadcast = "0.4" futures.workspace = true +image = "0.23" postage.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true serde_derive.workspace = true +smallvec.workspace = true [dev-dependencies] client = { package = "client2", path = "../client2", features = ["test-support"] } diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index e778316d59545f2ec81b553ddbb604cc3afcff1b..5933f7404f42d9dcab96261cea0cbdd5070ab073 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -1,6 +1,7 @@ pub mod call_settings; pub mod participant; pub mod room; +mod shared_screen; use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; @@ -14,7 +15,7 @@ use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ AppContext, AsyncAppContext, AsyncWindowContext, Context, EventEmitter, Model, ModelContext, - Subscription, Task, View, ViewContext, WeakModel, WeakView, + Subscription, Task, View, ViewContext, VisualContext, WeakModel, WeakView, }; pub use participant::ParticipantLocation; use participant::RemoteParticipant; @@ -23,6 +24,7 @@ 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}; @@ -587,24 +589,27 @@ impl CallHandler for Call { fn shared_screen_for_peer( &self, peer_id: PeerId, - _pane: &View, + pane: &View, cx: &mut ViewContext, ) -> Option> { let (call, _) = self.active_call.as_ref()?; + dbg!("A"); let room = call.read(cx).room()?.read(cx); + dbg!("B"); let participant = room.remote_participant_for_peer_id(peer_id)?; - let _track = participant.video_tracks.values().next()?.clone(); - let _user = participant.user.clone(); - todo!(); - // for item in pane.read(cx).items_of_type::() { - // if item.read(cx).peer_id == peer_id { - // return Box::new(Some(item)); - // } - // } - - // Some(Box::new(cx.build_view(|cx| { - // SharedScreen::new(&track, peer_id, user.clone(), cx) - // }))) + dbg!("C"); + let track = participant.video_tracks.values().next()?.clone(); + dbg!("D"); + 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()) @@ -629,7 +634,7 @@ impl CallHandler for Call { this.invite(called_user_id, initial_project, cx) }) } - fn remote_participants(&self, cx: &AppContext) -> Option>> { + fn remote_participants(&self, cx: &AppContext) -> Option, PeerId)>> { self.active_call .as_ref() .map(|call| { @@ -637,7 +642,9 @@ impl CallHandler for Call { room.read(cx) .remote_participants() .iter() - .map(|participant| participant.1.user.clone()) + .map(|participant| { + (participant.1.user.clone(), participant.1.peer_id.clone()) + }) .collect() }) }) @@ -665,6 +672,27 @@ impl CallHandler for Call { }) }); } + 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() { + dbg!("Unsharing"); + this.unshare_screen(cx); + } else { + dbg!("Sharing"); + let t = this.share_screen(cx); + cx.spawn(move |_, cx| async move { + t.await.log_err(); + }) + .detach(); + } + }) + }) + }) + }); + } } #[cfg(test)] diff --git a/crates/call2/src/participant.rs b/crates/call2/src/participant.rs index f62d103f1758d85f6e4a12cc10c1797c7cd80f50..325a4f812b2f58c1b1bb0cc56f042e891df435d4 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; -use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; +pub(crate) use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; use project::Project; use std::sync::Arc; diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index d73a522937615b9239877f7205d8f30d1bd82ad8..5bba4cc75063532125d6cd12fa97187241142138 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -1345,6 +1345,8 @@ impl Room { let display = displays .first() .ok_or_else(|| anyhow!("no display found"))?; + dbg!("Been there"); + dbg!(displays.len()); let track = LocalVideoTrack::screen_share_for_display(&display); this.upgrade() .ok_or_else(|| anyhow!("room was dropped"))? diff --git a/crates/call2/src/shared_screen.rs b/crates/call2/src/shared_screen.rs new file mode 100644 index 0000000000000000000000000000000000000000..a281948d6f10d682090d5c8d71c309d403424d01 --- /dev/null +++ b/crates/call2/src/shared_screen.rs @@ -0,0 +1,159 @@ +use crate::participant::{Frame, RemoteVideoTrack}; +use anyhow::Result; +use client::{proto::PeerId, User}; +use futures::StreamExt; +use gpui::{ + div, img, AppContext, Div, Element, Entity, EventEmitter, FocusHandle, FocusableView, + ImageData, Img, MouseButton, ParentElement, Render, SharedString, Task, View, ViewContext, + VisualContext, WindowContext, +}; +use smallvec::SmallVec; +use std::{ + borrow::Cow, + sync::{Arc, Weak}, +}; +use workspace::{ + item::{Item, ItemEvent}, + ItemNavHistory, WorkspaceId, +}; + +pub enum Event { + Close, +} + +pub struct SharedScreen { + track: Weak, + frame: Option, + pub peer_id: PeerId, + user: Arc, + nav_history: Option, + _maintain_frame: Task>, + focus: FocusHandle, +} + +impl SharedScreen { + pub fn new( + track: &Arc, + peer_id: PeerId, + user: Arc, + cx: &mut ViewContext, + ) -> Self { + cx.focus_handle(); + let mut frames = track.frames(); + Self { + track: Arc::downgrade(track), + frame: None, + peer_id, + user, + nav_history: Default::default(), + _maintain_frame: cx.spawn(|this, mut cx| async move { + while let Some(frame) = frames.next().await { + this.update(&mut cx, |this, cx| { + this.frame = Some(frame); + cx.notify(); + })?; + } + this.update(&mut cx, |_, cx| cx.emit(Event::Close))?; + Ok(()) + }), + focus: cx.focus_handle(), + } + } +} + +impl EventEmitter for SharedScreen {} +impl EventEmitter for SharedScreen {} + +impl FocusableView for SharedScreen { + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.focus.clone() + } +} +impl Render for SharedScreen { + type Element = Div; + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { + let frame = self.frame.clone(); + div().children(frame.map(|frame| { + img().data(Arc::new(ImageData::new(image::ImageBuffer::new( + frame.width() as u32, + frame.height() as u32, + )))) + })) + } +} +// impl View for SharedScreen { +// fn ui_name() -> &'static str { +// "SharedScreen" +// } + +// fn render(&mut self, cx: &mut ViewContext) -> AnyElement { +// enum Focus {} + +// let frame = self.frame.clone(); +// MouseEventHandler::new::(0, cx, |_, cx| { +// Canvas::new(move |bounds, _, _, cx| { +// if let Some(frame) = frame.clone() { +// let size = constrain_size_preserving_aspect_ratio( +// bounds.size(), +// vec2f(frame.width() as f32, frame.height() as f32), +// ); +// let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.; +// cx.scene().push_surface(gpui::platform::mac::Surface { +// bounds: RectF::new(origin, size), +// image_buffer: frame.image(), +// }); +// } +// }) +// .contained() +// .with_style(theme::current(cx).shared_screen) +// }) +// .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent()) +// .into_any() +// } +// } + +impl Item for SharedScreen { + fn tab_tooltip_text(&self, _: &AppContext) -> Option { + Some(format!("{}'s screen", self.user.github_login).into()) + } + fn deactivated(&mut self, cx: &mut ViewContext) { + if let Some(nav_history) = self.nav_history.as_mut() { + nav_history.push::<()>(None, cx); + } + } + + fn tab_content(&self, _: Option, _: &WindowContext<'_>) -> gpui::AnyElement { + div().child("Shared screen").into_any() + // Flex::row() + // .with_child( + // Svg::new("icons/desktop.svg") + // .with_color(style.label.text.color) + // .constrained() + // .with_width(style.type_icon_width) + // .aligned() + // .contained() + // .with_margin_right(style.spacing), + // ) + // .with_child( + // Label::new( + // format!("{}'s screen", self.user.github_login), + // style.label.clone(), + // ) + // .aligned(), + // ) + // .into_any() + } + + fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext) { + self.nav_history = Some(history); + } + + fn clone_on_split( + &self, + _workspace_id: WorkspaceId, + cx: &mut ViewContext, + ) -> Option> { + let track = self.track.upgrade()?; + Some(cx.build_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx))) + } +} diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index bcb43d4972b270b454e2e95741f1261643e51bfd..aad908531ac3a8b1362494778c27f2a99ea42391 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -31,9 +31,9 @@ use std::sync::Arc; use call::ActiveCall; use client::{Client, UserStore}; use gpui::{ - div, px, rems, AppContext, Div, InteractiveElement, IntoElement, Model, ParentElement, Render, - Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, - WeakView, WindowBounds, + div, px, rems, AppContext, Div, InteractiveElement, IntoElement, Model, MouseButton, + ParentElement, Render, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, + VisualContext, WeakView, WindowBounds, }; use project::Project; use theme::ActiveTheme; @@ -182,12 +182,22 @@ impl Render for CollabTitlebarItem { current_user .avatar .clone() - .map(|avatar| Avatar::new(avatar.clone())) + .map(|avatar| div().child(Avatar::new(avatar.clone()))) .into_iter() - .chain(remote_participants.into_iter().flat_map(|user| { - user.avatar - .as_ref() - .map(|avatar| Avatar::new(avatar.clone())) + .chain(remote_participants.into_iter().flat_map(|(user, peer_id)| { + user.avatar.as_ref().map(|avatar| { + div() + .child(Avatar::new(avatar.clone()).into_element()) + .on_mouse_down(MouseButton::Left, { + let workspace = workspace.clone(); + let id = peer_id.clone(); + move |_, cx| { + workspace.update(cx, |this, cx| { + this.open_shared_screen(peer_id, cx); + }); + } + }) + }) })), ) }, @@ -202,15 +212,22 @@ impl Render for CollabTitlebarItem { ) .child( h_stack() - .child(IconButton::new("mute-microphone", mic_icon).on_click( + .child(IconButton::new("mute-microphone", mic_icon).on_click({ + let workspace = workspace.clone(); move |_, cx| { workspace.update(cx, |this, cx| { this.call_state().toggle_mute(cx); }); + } + })) + .child(IconButton::new("mute-sound", ui::Icon::AudioOn)) + .child(IconButton::new("screen-share", ui::Icon::Screen).on_click( + move |_, cx| { + workspace.update(cx, |this, cx| { + this.call_state().toggle_screen_share(cx); + }); }, )) - .child(IconButton::new("mute-sound", ui::Icon::AudioOn)) - .child(IconButton::new("screen-share", ui::Icon::Screen)) .pl_2(), ), ) diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index bccb25b64ddc3a0cedce22b5e71a8ebcfccf551f..ac759022960bad85902dae3ff299b59fe3f17081 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -361,7 +361,7 @@ impl CallHandler for TestCallHandler { unimplemented!() } - fn remote_participants(&self, cx: &AppContext) -> Option> { + fn remote_participants(&self, cx: &AppContext) -> Option, PeerId)>> { None } @@ -370,6 +370,8 @@ impl CallHandler for TestCallHandler { } fn toggle_mute(&self, cx: &mut AppContext) {} + + fn toggle_screen_share(&self, cx: &mut AppContext) {} } impl AppState { @@ -480,9 +482,10 @@ pub trait CallHandler { initial_project: Option>, cx: &mut AppContext, ) -> Task>; - fn remote_participants(&self, cx: &AppContext) -> Option>>; + fn remote_participants(&self, cx: &AppContext) -> Option, PeerId)>>; fn is_muted(&self, cx: &AppContext) -> Option; fn toggle_mute(&self, cx: &mut AppContext); + fn toggle_screen_share(&self, cx: &mut AppContext); } pub struct Workspace { @@ -1996,13 +1999,15 @@ impl Workspace { item } - // 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(Box::new(shared_screen), false, true, None, cx) - // }); - // } - // } + 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) + }); + } else { + dbg!("WTF"); + } + } pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext) -> bool { let result = self.panes.iter().find_map(|pane| { @@ -2877,10 +2882,10 @@ impl Workspace { } continue; } - // todo!() - // if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { - // items_to_activate.push((pane.clone(), Box::new(shared_screen))); - // } + + if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) { + items_to_activate.push((pane.clone(), shared_screen)); + } } for (pane, item) in items_to_activate { @@ -2901,27 +2906,27 @@ impl Workspace { None } - // todo!() - // fn shared_screen_for_peer( - // &self, - // peer_id: PeerId, - // pane: &View, - // cx: &mut ViewContext, - // ) -> 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); - // } - // } + fn shared_screen_for_peer( + &self, + 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(); + + // 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) { if cx.is_window_active() {