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