Detailed changes
@@ -170,17 +170,6 @@ dependencies = [
"rust-embed",
]
-[[package]]
-name = "async-broadcast"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90622698a1218e0b2fb846c97b5f19a0831f6baddee73d9454156365ccfa473b"
-dependencies = [
- "easy-parallel",
- "event-listener",
- "futures-core",
-]
-
[[package]]
name = "async-broadcast"
version = "0.4.1"
@@ -727,6 +716,7 @@ name = "call"
version = "0.1.0"
dependencies = [
"anyhow",
+ "async-broadcast",
"client",
"collections",
"futures 0.3.24",
@@ -2983,7 +2973,7 @@ name = "language"
version = "0.1.0"
dependencies = [
"anyhow",
- "async-broadcast 0.3.4",
+ "async-broadcast",
"async-trait",
"client",
"clock",
@@ -3156,7 +3146,7 @@ name = "live_kit_client"
version = "0.1.0"
dependencies = [
"anyhow",
- "async-broadcast 0.4.1",
+ "async-broadcast",
"async-trait",
"block",
"byteorder",
@@ -27,6 +27,7 @@ project = { path = "../project" }
util = { path = "../util" }
anyhow = "1.0.38"
+async-broadcast = "0.4"
futures = "0.3"
postage = { version = "0.4.1", features = ["futures-traits"] }
@@ -1,4 +1,4 @@
-mod participant;
+pub mod participant;
pub mod room;
use anyhow::{anyhow, Result};
@@ -1,8 +1,8 @@
use anyhow::{anyhow, Result};
use client::{proto, User};
use collections::HashMap;
-use gpui::{Task, WeakModelHandle};
-use live_kit_client::Frame;
+use gpui::WeakModelHandle;
+pub use live_kit_client::Frame;
use project::Project;
use std::sync::Arc;
@@ -41,18 +41,16 @@ pub struct RemoteParticipant {
pub user: Arc<User>,
pub projects: Vec<proto::ParticipantProject>,
pub location: ParticipantLocation,
- pub tracks: HashMap<live_kit_client::Sid, RemoteVideoTrack>,
+ pub tracks: HashMap<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
}
#[derive(Clone)]
pub struct RemoteVideoTrack {
- pub(crate) frame: Option<Frame>,
- pub(crate) _live_kit_track: Arc<live_kit_client::RemoteVideoTrack>,
- pub(crate) _maintain_frame: Arc<Task<()>>,
+ pub(crate) live_kit_track: Arc<live_kit_client::RemoteVideoTrack>,
}
impl RemoteVideoTrack {
- pub fn frame(&self) -> Option<&Frame> {
- self.frame.as_ref()
+ pub fn frames(&self) -> async_broadcast::Receiver<Frame> {
+ self.live_kit_track.frames()
}
}
@@ -7,7 +7,7 @@ use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
use collections::{BTreeMap, HashSet};
use futures::StreamExt;
use gpui::{AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task};
-use live_kit_client::{LocalTrackPublication, LocalVideoTrack, RemoteVideoTrackUpdate};
+use live_kit_client::{LocalTrackPublication, LocalVideoTrack, RemoteVideoTrackUpdate, Sid};
use postage::stream::Stream;
use project::Project;
use std::{mem, os::unix::prelude::OsStrExt, sync::Arc};
@@ -15,9 +15,16 @@ use util::{post_inc, ResultExt};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
- Frame {
+ ParticipantLocationChanged {
participant_id: PeerId,
- track_id: live_kit_client::Sid,
+ },
+ RemoteVideoTrackShared {
+ participant_id: PeerId,
+ track_id: Sid,
+ },
+ RemoteVideoTrackUnshared {
+ peer_id: PeerId,
+ track_id: Sid,
},
RemoteProjectShared {
owner: Arc<User>,
@@ -356,7 +363,12 @@ impl Room {
if let Some(remote_participant) = this.remote_participants.get_mut(&peer_id)
{
remote_participant.projects = participant.projects;
- remote_participant.location = location;
+ if location != remote_participant.location {
+ remote_participant.location = location;
+ cx.emit(Event::ParticipantLocationChanged {
+ participant_id: peer_id,
+ });
+ }
} else {
this.remote_participants.insert(
peer_id,
@@ -430,44 +442,16 @@ impl Room {
.remote_participants
.get_mut(&peer_id)
.ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?;
- let mut frames = track.frames();
participant.tracks.insert(
track_id.clone(),
- RemoteVideoTrack {
- frame: None,
- _live_kit_track: track,
- _maintain_frame: Arc::new(cx.spawn_weak(|this, mut cx| async move {
- while let Some(frame) = frames.next().await {
- let this = if let Some(this) = this.upgrade(&cx) {
- this
- } else {
- break;
- };
-
- let done = this.update(&mut cx, |this, cx| {
- if let Some(track) =
- this.remote_participants.get_mut(&peer_id).and_then(
- |participant| participant.tracks.get_mut(&track_id),
- )
- {
- track.frame = Some(frame);
- cx.emit(Event::Frame {
- participant_id: peer_id,
- track_id: track_id.clone(),
- });
- false
- } else {
- true
- }
- });
-
- if done {
- break;
- }
- }
- })),
- },
+ Arc::new(RemoteVideoTrack {
+ live_kit_track: track,
+ }),
);
+ cx.emit(Event::RemoteVideoTrackShared {
+ participant_id: peer_id,
+ track_id,
+ });
}
RemoteVideoTrackUpdate::Unsubscribed {
publisher_id,
@@ -479,6 +463,7 @@ impl Room {
.get_mut(&peer_id)
.ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?;
participant.tracks.remove(&track_id);
+ cx.emit(Event::RemoteVideoTrackUnshared { peer_id, track_id });
}
}
@@ -750,7 +735,7 @@ struct LiveKitRoom {
_maintain_tracks: Task<()>,
}
-pub enum ScreenTrack {
+enum ScreenTrack {
None,
Pending { publish_id: usize },
Published(LocalTrackPublication),
@@ -5,10 +5,7 @@ use crate::{
};
use ::rpc::Peer;
use anyhow::anyhow;
-use call::{
- room::{self, Event},
- ActiveCall, ParticipantLocation, Room,
-};
+use call::{room, ActiveCall, ParticipantLocation, Room};
use client::{
self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Connection,
Credentials, EstablishConnectionError, PeerId, User, UserStore, RECEIVE_TIMEOUT,
@@ -33,7 +30,7 @@ use language::{
range_to_lsp, tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
LanguageConfig, LanguageRegistry, OffsetRangeExt, Point, Rope,
};
-use live_kit_client::{Frame, MacOSDisplay};
+use live_kit_client::MacOSDisplay;
use lsp::{self, FakeLanguageServer};
use parking_lot::Mutex;
use project::{
@@ -202,36 +199,25 @@ async fn test_basic_calls(
.await
.unwrap();
- let frame = Frame {
- width: 800,
- height: 600,
- label: "a".into(),
- };
- display.send_frame(frame.clone());
deterministic.run_until_parked();
assert_eq!(events_b.borrow().len(), 1);
let event = events_b.borrow().first().unwrap().clone();
- if let Event::Frame {
+ if let call::room::Event::RemoteVideoTrackShared {
participant_id,
track_id,
} = event
{
assert_eq!(participant_id, client_a.peer_id().unwrap());
room_b.read_with(cx_b, |room, _| {
- assert_eq!(
- room.remote_participants()[&client_a.peer_id().unwrap()].tracks[&track_id].frame(),
- Some(&frame)
- );
+ assert!(room.remote_participants()[&client_a.peer_id().unwrap()]
+ .tracks
+ .contains_key(&track_id));
});
} else {
panic!("unexpected event")
}
- display.send_frame(frame.clone());
- deterministic.run_until_parked();
- assert_eq!(events_b.borrow().len(), 2);
-
// User A leaves the room.
active_call_a.update(cx_a, |call, cx| {
call.hang_up(cx).unwrap();
@@ -36,7 +36,7 @@ text = { path = "../text" }
theme = { path = "../theme" }
util = { path = "../util" }
anyhow = "1.0.38"
-async-broadcast = "0.3.4"
+async-broadcast = "0.4"
async-trait = "0.1"
futures = "0.3"
lazy_static = "1.4"
@@ -2,9 +2,7 @@ use crate::{FollowerStatesByLeader, JoinProject, Pane, Workspace};
use anyhow::{anyhow, Result};
use call::{ActiveCall, ParticipantLocation};
use gpui::{
- elements::*,
- geometry::{rect::RectF, vector::vec2f},
- Axis, Border, CursorStyle, ModelHandle, MouseButton, RenderContext, ViewHandle,
+ elements::*, Axis, Border, CursorStyle, ModelHandle, MouseButton, RenderContext, ViewHandle,
};
use project::Project;
use serde::Deserialize;
@@ -144,30 +142,6 @@ impl Member {
Border::default()
};
- let content = if leader.as_ref().map_or(false, |(_, leader)| {
- leader.location == ParticipantLocation::External && !leader.tracks.is_empty()
- }) {
- let (_, leader) = leader.unwrap();
- let track = leader.tracks.values().next().unwrap();
- let frame = track.frame().cloned();
- 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::mac::Surface {
- bounds: RectF::new(origin, size),
- image_buffer: frame.image(),
- });
- }
- })
- .boxed()
- } else {
- ChildView::new(pane, cx).boxed()
- };
-
let prompt = if let Some((_, leader)) = leader {
match leader.location {
ParticipantLocation::SharedProject {
@@ -251,7 +225,12 @@ impl Member {
};
Stack::new()
- .with_child(Container::new(content).with_border(border).boxed())
+ .with_child(
+ ChildView::new(pane, cx)
+ .contained()
+ .with_border(border)
+ .boxed(),
+ )
.with_children(prompt)
.boxed()
}
@@ -0,0 +1,175 @@
+use crate::{Item, ItemNavHistory};
+use anyhow::{anyhow, Result};
+use call::participant::{Frame, RemoteVideoTrack};
+use client::{PeerId, User};
+use futures::StreamExt;
+use gpui::{
+ elements::*,
+ geometry::{rect::RectF, vector::vec2f},
+ Entity, ModelHandle, RenderContext, Task, View, ViewContext,
+};
+use smallvec::SmallVec;
+use std::{
+ path::PathBuf,
+ sync::{Arc, Weak},
+};
+
+pub enum Event {
+ Close,
+}
+
+pub struct SharedScreen {
+ track: Weak<RemoteVideoTrack>,
+ frame: Option<Frame>,
+ pub peer_id: PeerId,
+ user: Arc<User>,
+ nav_history: Option<ItemNavHistory>,
+ _maintain_frame: Task<()>,
+}
+
+impl SharedScreen {
+ pub fn new(
+ track: &Arc<RemoteVideoTrack>,
+ peer_id: PeerId,
+ user: Arc<User>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ 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));
+ }),
+ }
+ }
+}
+
+impl Entity for SharedScreen {
+ type Event = Event;
+}
+
+impl View for SharedScreen {
+ fn ui_name() -> &'static str {
+ "SharedScreen"
+ }
+
+ fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+ let frame = self.frame.clone();
+ 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::mac::Surface {
+ bounds: RectF::new(origin, size),
+ image_buffer: frame.image(),
+ });
+ }
+ })
+ .boxed()
+ }
+}
+
+impl Item for SharedScreen {
+ fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(nav_history) = self.nav_history.as_ref() {
+ nav_history.push::<()>(None, cx);
+ }
+ }
+
+ fn tab_content(
+ &self,
+ _: Option<usize>,
+ style: &theme::Tab,
+ _: &gpui::AppContext,
+ ) -> gpui::ElementBox {
+ Flex::row()
+ .with_child(
+ Svg::new("icons/disable_screen_sharing_12.svg")
+ .with_color(style.label.text.color)
+ .constrained()
+ .with_width(style.icon_width)
+ .aligned()
+ .contained()
+ .with_margin_right(style.spacing)
+ .boxed(),
+ )
+ .with_child(
+ Label::new(
+ format!("{}'s screen", self.user.github_login),
+ style.label.clone(),
+ )
+ .aligned()
+ .boxed(),
+ )
+ .boxed()
+ }
+
+ fn project_path(&self, _: &gpui::AppContext) -> Option<project::ProjectPath> {
+ Default::default()
+ }
+
+ fn project_entry_ids(&self, _: &gpui::AppContext) -> SmallVec<[project::ProjectEntryId; 3]> {
+ Default::default()
+ }
+
+ fn is_singleton(&self, _: &gpui::AppContext) -> bool {
+ false
+ }
+
+ fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
+ self.nav_history = Some(history);
+ }
+
+ fn clone_on_split(&self, cx: &mut ViewContext<Self>) -> Option<Self> {
+ let track = self.track.upgrade()?;
+ Some(Self::new(&track, self.peer_id, self.user.clone(), cx))
+ }
+
+ fn can_save(&self, _: &gpui::AppContext) -> bool {
+ false
+ }
+
+ fn save(
+ &mut self,
+ _: ModelHandle<project::Project>,
+ _: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ Task::ready(Err(anyhow!("Item::save called on SharedScreen")))
+ }
+
+ fn save_as(
+ &mut self,
+ _: ModelHandle<project::Project>,
+ _: PathBuf,
+ _: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ Task::ready(Err(anyhow!("Item::save_as called on SharedScreen")))
+ }
+
+ fn reload(
+ &mut self,
+ _: ModelHandle<project::Project>,
+ _: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ Task::ready(Err(anyhow!("Item::reload called on SharedScreen")))
+ }
+
+ fn to_item_events(event: &Self::Event) -> Vec<crate::ItemEvent> {
+ match event {
+ Event::Close => vec![crate::ItemEvent::CloseItem],
+ }
+ }
+}
@@ -6,6 +6,7 @@ pub mod dock;
pub mod pane;
pub mod pane_group;
pub mod searchable;
+mod shared_screen;
pub mod sidebar;
mod status_bar;
mod toolbar;
@@ -36,6 +37,7 @@ use project::{Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, Work
use searchable::SearchableItemHandle;
use serde::Deserialize;
use settings::{Autosave, DockAnchor, Settings};
+use shared_screen::SharedScreen;
use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem};
use smallvec::SmallVec;
use status_bar::StatusBar;
@@ -1097,14 +1099,7 @@ impl Workspace {
if cx.has_global::<ModelHandle<ActiveCall>>() {
let call = cx.global::<ModelHandle<ActiveCall>>().clone();
let mut subscriptions = Vec::new();
- subscriptions.push(cx.observe(&call, |_, _, cx| cx.notify()));
- subscriptions.push(cx.subscribe(&call, |this, _, event, cx| {
- if let call::room::Event::Frame { participant_id, .. } = event {
- if this.follower_states_by_leader.contains_key(&participant_id) {
- cx.notify();
- }
- }
- }));
+ subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
active_call = Some((call, subscriptions));
}
@@ -2517,13 +2512,43 @@ impl Workspace {
}
fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
+ cx.notify();
+
+ let call = self.active_call()?;
+ let room = call.read(cx).room()?.read(cx);
+ let participant = room.remote_participants().get(&leader_id)?;
+
let mut items_to_add = Vec::new();
- for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
- if let Some(FollowerItem::Loaded(item)) = state
- .active_view_id
- .and_then(|id| state.items_by_leader_view_id.get(&id))
- {
- items_to_add.push((pane.clone(), item.boxed_clone()));
+ match participant.location {
+ call::ParticipantLocation::SharedProject { project_id } => {
+ if Some(project_id) == self.project.read(cx).remote_id() {
+ for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
+ if let Some(FollowerItem::Loaded(item)) = state
+ .active_view_id
+ .and_then(|id| state.items_by_leader_view_id.get(&id))
+ {
+ items_to_add.push((pane.clone(), item.boxed_clone()));
+ }
+ }
+ }
+ }
+ call::ParticipantLocation::UnsharedProject => {}
+ call::ParticipantLocation::External => {
+ let track = participant.tracks.values().next()?.clone();
+ let user = participant.user.clone();
+
+ 'outer: for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
+ for item in pane.read(cx).items_of_type::<SharedScreen>() {
+ if item.read(cx).peer_id == leader_id {
+ items_to_add.push((pane.clone(), Box::new(item)));
+ continue 'outer;
+ }
+ }
+
+ let shared_screen =
+ cx.add_view(|cx| SharedScreen::new(&track, leader_id, user.clone(), cx));
+ items_to_add.push((pane.clone(), Box::new(shared_screen)));
+ }
}
}
@@ -2532,8 +2557,8 @@ impl Workspace {
if pane == self.active_pane {
pane.update(cx, |pane, cx| pane.focus_active_item(cx));
}
- cx.notify();
}
+
None
}
@@ -2561,6 +2586,27 @@ impl Workspace {
fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
self.active_call.as_ref().map(|(call, _)| call)
}
+
+ fn on_active_call_event(
+ &mut self,
+ _: ModelHandle<ActiveCall>,
+ event: &call::room::Event,
+ cx: &mut ViewContext<Self>,
+ ) {
+ match event {
+ call::room::Event::ParticipantLocationChanged {
+ participant_id: peer_id,
+ }
+ | call::room::Event::RemoteVideoTrackShared {
+ participant_id: peer_id,
+ ..
+ }
+ | call::room::Event::RemoteVideoTrackUnshared { peer_id, .. } => {
+ self.leader_updated(*peer_id, cx);
+ }
+ _ => {}
+ }
+ }
}
impl Entity for Workspace {