diff --git a/assets/icons/disable_screen_sharing_12.svg b/assets/icons/disable_screen_sharing_12.svg
new file mode 100644
index 0000000000..c2a4edd45b
--- /dev/null
+++ b/assets/icons/disable_screen_sharing_12.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/enable_screen_sharing_12.svg b/assets/icons/enable_screen_sharing_12.svg
new file mode 100644
index 0000000000..6ae37649d2
--- /dev/null
+++ b/assets/icons/enable_screen_sharing_12.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs
index 056c6f1260..6b06d04375 100644
--- a/crates/call/src/call.rs
+++ b/crates/call/src/call.rs
@@ -132,8 +132,6 @@ impl ActiveCall {
Room::create(recipient_user_id, initial_project, client, user_store, cx)
})
.await?;
- room.update(&mut cx, |room, cx| room.share_screen(cx))
- .await?;
this.update(&mut cx, |this, cx| this.set_room(Some(room), cx));
};
diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs
index 86946b7974..4553772095 100644
--- a/crates/call/src/room.rs
+++ b/crates/call/src/room.rs
@@ -606,6 +606,12 @@ impl Room {
})
}
+ pub fn is_screen_sharing(&self) -> bool {
+ self.live_kit
+ .as_ref()
+ .map_or(false, |live_kit| live_kit.screen_track.is_some())
+ }
+
pub fn share_screen(&mut self, cx: &mut ModelContext) -> Task> {
if self.status.is_offline() {
return Task::ready(Err(anyhow!("room is offline")));
@@ -617,22 +623,49 @@ impl Room {
.first()
.ok_or_else(|| anyhow!("no display found"))?;
let track = LocalVideoTrack::screen_share_for_display(&display);
-
let publication = this
- .upgrade(&cx)?
+ .upgrade(&cx)
+ .ok_or_else(|| anyhow!("room was dropped"))?
.read_with(&cx, |this, _| {
this.live_kit
.as_ref()
.map(|live_kit| live_kit.room.publish_video_track(&track))
- })?
+ })
+ .ok_or_else(|| anyhow!("live-kit was not initialized"))?
.await?;
- this.upgrade(&cx)?.update(cx, |this, _| {
- this.live_kit.as_mut()?.screen_track = Some(publication);
- Some(())
- })
+ this.upgrade(&cx)
+ .ok_or_else(|| anyhow!("room was dropped"))?
+ .update(&mut cx, |this, cx| {
+ let live_kit = this
+ .live_kit
+ .as_mut()
+ .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
+ live_kit.screen_track = Some(publication);
+ cx.notify();
+ Ok(())
+ })
})
}
+
+ pub fn unshare_screen(&mut self, cx: &mut ModelContext) -> Result<()> {
+ if self.status.is_offline() {
+ return Err(anyhow!("room is offline"));
+ }
+
+ let live_kit = self
+ .live_kit
+ .as_mut()
+ .ok_or_else(|| anyhow!("live-kit was not initialized"))?;
+ let track = live_kit
+ .screen_track
+ .take()
+ .ok_or_else(|| anyhow!("screen was not shared"))?;
+ live_kit.room.unpublish_track(track);
+ cx.notify();
+
+ Ok(())
+ }
}
struct LiveKitRoom {
diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs
index 1279d30437..43d9bc0dbf 100644
--- a/crates/collab_ui/src/collab_titlebar_item.rs
+++ b/crates/collab_ui/src/collab_titlebar_item.rs
@@ -10,17 +10,21 @@ use gpui::{
geometry::{rect::RectF, vector::vec2f, PathBuilder},
json::{self, ToJson},
Border, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext,
- Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
+ Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use settings::Settings;
use std::ops::Range;
use theme::Theme;
use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace};
-actions!(collab, [ToggleCollaborationMenu, ShareProject]);
+actions!(
+ collab,
+ [ToggleCollaborationMenu, ToggleScreenSharing, ShareProject]
+);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
+ cx.add_action(CollabTitlebarItem::toggle_screen_sharing);
cx.add_action(CollabTitlebarItem::share_project);
}
@@ -48,10 +52,12 @@ impl View for CollabTitlebarItem {
};
let theme = cx.global::().theme.clone();
- let project = workspace.read(cx).project().read(cx);
let mut container = Flex::row();
+ container.add_children(self.render_toggle_screen_sharing_button(&theme, cx));
+
if workspace.read(cx).client().status().borrow().is_connected() {
+ let project = workspace.read(cx).project().read(cx);
if project.is_shared()
|| project.is_remote()
|| ActiveCall::global(cx).read(cx).room().is_none()
@@ -169,6 +175,19 @@ impl CollabTitlebarItem {
cx.notify();
}
+ pub fn toggle_screen_sharing(&mut self, _: &ToggleScreenSharing, cx: &mut ViewContext) {
+ if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+ let toggle_screen_sharing = room.update(cx, |room, cx| {
+ if room.is_screen_sharing() {
+ Task::ready(room.unshare_screen(cx))
+ } else {
+ room.share_screen(cx)
+ }
+ });
+ toggle_screen_sharing.detach_and_log_err(cx);
+ }
+ }
+
fn render_toggle_contacts_button(
&self,
theme: &Theme,
@@ -237,6 +256,43 @@ impl CollabTitlebarItem {
.boxed()
}
+ fn render_toggle_screen_sharing_button(
+ &self,
+ theme: &Theme,
+ cx: &mut RenderContext,
+ ) -> Option {
+ let active_call = ActiveCall::global(cx);
+ let room = active_call.read(cx).room().cloned()?;
+ let icon = if room.read(cx).is_screen_sharing() {
+ "icons/disable_screen_sharing_12.svg"
+ } else {
+ "icons/enable_screen_sharing_12.svg"
+ };
+ let titlebar = &theme.workspace.titlebar;
+ Some(
+ MouseEventHandler::::new(0, cx, |state, _| {
+ let style = titlebar.call_control.style_for(state, false);
+ Svg::new(icon)
+ .with_color(style.color)
+ .constrained()
+ .with_width(style.icon_width)
+ .aligned()
+ .constrained()
+ .with_width(style.button_width)
+ .with_height(style.button_width)
+ .contained()
+ .with_style(style.container)
+ .boxed()
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, move |_, cx| {
+ cx.dispatch_action(ToggleScreenSharing);
+ })
+ .aligned()
+ .boxed(),
+ )
+ }
+
fn render_share_button(&self, theme: &Theme, cx: &mut RenderContext) -> ElementBox {
enum Share {}
diff --git a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift
index 7488eb9444..8bfa98b522 100644
--- a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift
+++ b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift
@@ -95,6 +95,13 @@ public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPoin
}
}
+@_cdecl("LKRoomUnpublishTrack")
+public func LKRoomUnpublishTrack(room: UnsafeRawPointer, publication: UnsafeRawPointer) {
+ let room = Unmanaged.fromOpaque(room).takeUnretainedValue()
+ let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue()
+ room.localParticipant?.unpublish(publication: publication)
+}
+
@_cdecl("LKRoomVideoTracksForRemoteParticipant")
public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
let room = Unmanaged.fromOpaque(room).takeUnretainedValue()
diff --git a/crates/live_kit_client/src/live_kit_client.rs b/crates/live_kit_client/src/live_kit_client.rs
index b3724e91db..6532ce110a 100644
--- a/crates/live_kit_client/src/live_kit_client.rs
+++ b/crates/live_kit_client/src/live_kit_client.rs
@@ -48,6 +48,7 @@ extern "C" {
callback: extern "C" fn(*mut c_void, *mut c_void, CFStringRef),
callback_data: *mut c_void,
);
+ fn LKRoomUnpublishTrack(room: *const c_void, publication: *const c_void);
fn LKRoomVideoTracksForRemoteParticipant(
room: *const c_void,
participant_id: CFStringRef,
@@ -134,6 +135,12 @@ impl Room {
async { rx.await.unwrap().context("error publishing video track") }
}
+ pub fn unpublish_track(&self, publication: LocalTrackPublication) {
+ unsafe {
+ LKRoomUnpublishTrack(self.native_room, publication.0);
+ }
+ }
+
pub fn remote_video_tracks(&self, participant_id: &str) -> Vec> {
unsafe {
let tracks = LKRoomVideoTracksForRemoteParticipant(
diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs
index 51aa3232d4..e517f8ced0 100644
--- a/crates/theme/src/theme.rs
+++ b/crates/theme/src/theme.rs
@@ -79,6 +79,7 @@ pub struct Titlebar {
pub sign_in_prompt: Interactive,
pub outdated_warning: ContainedText,
pub share_button: Interactive,
+ pub call_control: Interactive,
pub toggle_contacts_button: Interactive,
pub toggle_contacts_badge: ContainerStyle,
}
diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts
index c8d32ae1a8..0defabbd7f 100644
--- a/styles/src/styleTree/workspace.ts
+++ b/styles/src/styleTree/workspace.ts
@@ -138,7 +138,18 @@ export default function workspace(theme: Theme) {
},
cornerRadius: 6,
},
+ callControl: {
+ cornerRadius: 6,
+ color: iconColor(theme, "secondary"),
+ iconWidth: 12,
+ buttonWidth: 20,
+ hover: {
+ background: backgroundColor(theme, "on300", "hovered"),
+ color: iconColor(theme, "active"),
+ },
+ },
toggleContactsButton: {
+ margin: { left: 6 },
cornerRadius: 6,
color: iconColor(theme, "secondary"),
iconWidth: 8,