1mod live_kit_token;
2
3use futures::StreamExt;
4use gpui::{
5 actions,
6 elements::{Canvas, *},
7 keymap::Binding,
8 platform::current::Surface,
9 Menu, MenuItem, ViewContext,
10};
11use live_kit::{LocalVideoTrack, Room};
12use log::LevelFilter;
13use media::core_video::CVImageBuffer;
14use postage::watch;
15use simplelog::SimpleLogger;
16use std::sync::Arc;
17
18actions!(capture, [Quit]);
19
20fn main() {
21 SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
22
23 gpui::App::new(()).unwrap().run(|cx| {
24 cx.platform().activate(true);
25 cx.add_global_action(quit);
26
27 cx.add_bindings([Binding::new("cmd-q", Quit, None)]);
28 cx.set_menus(vec![Menu {
29 name: "Zed",
30 items: vec![MenuItem::Action {
31 name: "Quit",
32 action: Box::new(Quit),
33 }],
34 }]);
35
36 let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap();
37 let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap();
38 let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap();
39
40 cx.spawn(|mut cx| async move {
41 let user1_token = live_kit_token::create_token(
42 &live_kit_key,
43 &live_kit_secret,
44 "test-room",
45 "test-participant-1",
46 )
47 .unwrap();
48 let room1 = Room::new();
49 room1.connect(&live_kit_url, &user1_token).await.unwrap();
50
51 let user2_token = live_kit_token::create_token(
52 &live_kit_key,
53 &live_kit_secret,
54 "test-room",
55 "test-participant-2",
56 )
57 .unwrap();
58 let room2 = Room::new();
59 room2.connect(&live_kit_url, &user2_token).await.unwrap();
60 cx.add_window(Default::default(), |cx| ScreenCaptureView::new(room2, cx));
61
62 let windows = live_kit::list_windows();
63 let window = windows
64 .iter()
65 .find(|w| w.owner_name.as_deref() == Some("Safari"))
66 .unwrap();
67 let track = LocalVideoTrack::screen_share_for_window(window.id);
68 room1.publish_video_track(&track).await.unwrap();
69 })
70 .detach();
71 });
72}
73
74struct ScreenCaptureView {
75 image_buffer: Option<CVImageBuffer>,
76 _room: Arc<Room>,
77}
78
79impl gpui::Entity for ScreenCaptureView {
80 type Event = ();
81}
82
83impl ScreenCaptureView {
84 pub fn new(room: Arc<Room>, cx: &mut ViewContext<Self>) -> Self {
85 let mut remote_video_tracks = room.remote_video_tracks();
86 cx.spawn_weak(|this, mut cx| async move {
87 if let Some(video_track) = remote_video_tracks.next().await {
88 let (mut frames_tx, mut frames_rx) = watch::channel_with(None);
89 video_track.add_renderer(move |frame| *frames_tx.borrow_mut() = Some(frame));
90
91 while let Some(frame) = frames_rx.next().await {
92 if let Some(this) = this.upgrade(&cx) {
93 this.update(&mut cx, |this, cx| {
94 this.image_buffer = frame;
95 cx.notify();
96 });
97 } else {
98 break;
99 }
100 }
101 }
102 })
103 .detach();
104
105 Self {
106 image_buffer: None,
107 _room: room,
108 }
109 }
110}
111
112impl gpui::View for ScreenCaptureView {
113 fn ui_name() -> &'static str {
114 "View"
115 }
116
117 fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
118 let image_buffer = self.image_buffer.clone();
119 let canvas = Canvas::new(move |bounds, _, cx| {
120 if let Some(image_buffer) = image_buffer.clone() {
121 cx.scene.push_surface(Surface {
122 bounds,
123 image_buffer,
124 });
125 }
126 });
127
128 if let Some(image_buffer) = self.image_buffer.as_ref() {
129 canvas
130 .constrained()
131 .with_width(image_buffer.width() as f32)
132 .with_height(image_buffer.height() as f32)
133 .aligned()
134 .boxed()
135 } else {
136 canvas.boxed()
137 }
138 }
139}
140
141fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
142 cx.platform().quit();
143}