1use anyhow::{anyhow, Context, Result};
2use core_foundation::{
3 array::{CFArray, CFArrayRef},
4 base::{TCFType, TCFTypeRef},
5 dictionary::CFDictionary,
6 number::CFNumber,
7 string::{CFString, CFStringRef},
8};
9use core_graphics::window::{
10 kCGNullWindowID, kCGWindowListOptionExcludeDesktopElements, kCGWindowListOptionOnScreenOnly,
11 kCGWindowNumber, kCGWindowOwnerName, kCGWindowOwnerPID, CGWindowListCopyWindowInfo,
12};
13use futures::{
14 channel::{mpsc, oneshot},
15 Future,
16};
17use media::core_video::{CVImageBuffer, CVImageBufferRef};
18use parking_lot::Mutex;
19use std::{
20 ffi::c_void,
21 sync::{Arc, Weak},
22};
23
24extern "C" {
25 fn LKRelease(object: *const c_void);
26
27 fn LKRoomDelegateCreate(
28 callback_data: *mut c_void,
29 on_did_subscribe_to_remote_video_track: extern "C" fn(
30 callback_data: *mut c_void,
31 remote_track: *const c_void,
32 ),
33 ) -> *const c_void;
34
35 fn LKRoomCreate(delegate: *const c_void) -> *const c_void;
36 fn LKRoomConnect(
37 room: *const c_void,
38 url: CFStringRef,
39 token: CFStringRef,
40 callback: extern "C" fn(*mut c_void, CFStringRef),
41 callback_data: *mut c_void,
42 );
43 fn LKRoomPublishVideoTrack(
44 room: *const c_void,
45 track: *const c_void,
46 callback: extern "C" fn(*mut c_void, CFStringRef),
47 callback_data: *mut c_void,
48 );
49
50 fn LKVideoRendererCreate(
51 callback_data: *mut c_void,
52 on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef),
53 on_drop: extern "C" fn(callback_data: *mut c_void),
54 ) -> *const c_void;
55
56 fn LKVideoTrackAddRenderer(track: *const c_void, renderer: *const c_void);
57
58 fn LKCreateScreenShareTrackForWindow(windowId: u32) -> *const c_void;
59 fn LKDisplaySources(
60 callback_data: *mut c_void,
61 callback: extern "C" fn(
62 callback_data: *mut c_void,
63 sources: CFArrayRef,
64 error: CFStringRef,
65 ),
66 );
67}
68
69pub struct Room {
70 native_room: *const c_void,
71 remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<Arc<RemoteVideoTrack>>>>,
72 _delegate: RoomDelegate,
73}
74
75impl Room {
76 pub fn new() -> Arc<Self> {
77 Arc::new_cyclic(|weak_room| {
78 let delegate = RoomDelegate::new(weak_room.clone());
79 Self {
80 native_room: unsafe { LKRoomCreate(delegate.native_delegate) },
81 remote_video_track_subscribers: Default::default(),
82 _delegate: delegate,
83 }
84 })
85 }
86
87 pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> {
88 let url = CFString::new(url);
89 let token = CFString::new(token);
90 let (did_connect, tx, rx) = Self::build_done_callback();
91 unsafe {
92 LKRoomConnect(
93 self.native_room,
94 url.as_concrete_TypeRef(),
95 token.as_concrete_TypeRef(),
96 did_connect,
97 tx,
98 )
99 }
100
101 async { rx.await.unwrap().context("error connecting to room") }
102 }
103
104 pub fn publish_video_track(&self, track: &LocalVideoTrack) -> impl Future<Output = Result<()>> {
105 let (did_publish, tx, rx) = Self::build_done_callback();
106 unsafe {
107 LKRoomPublishVideoTrack(self.native_room, track.0, did_publish, tx);
108 }
109 async { rx.await.unwrap().context("error publishing video track") }
110 }
111
112 pub fn remote_video_tracks(&self) -> mpsc::UnboundedReceiver<Arc<RemoteVideoTrack>> {
113 let (tx, rx) = mpsc::unbounded();
114 self.remote_video_track_subscribers.lock().push(tx);
115 rx
116 }
117
118 fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
119 let track = Arc::new(track);
120 self.remote_video_track_subscribers
121 .lock()
122 .retain(|tx| tx.unbounded_send(track.clone()).is_ok());
123 }
124
125 fn build_done_callback() -> (
126 extern "C" fn(*mut c_void, CFStringRef),
127 *mut c_void,
128 oneshot::Receiver<Result<()>>,
129 ) {
130 let (tx, rx) = oneshot::channel();
131 extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
132 let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
133 if error.is_null() {
134 let _ = tx.send(Ok(()));
135 } else {
136 let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
137 let _ = tx.send(Err(anyhow!(error)));
138 }
139 }
140 (
141 done_callback,
142 Box::into_raw(Box::new(tx)) as *mut c_void,
143 rx,
144 )
145 }
146}
147
148impl Drop for Room {
149 fn drop(&mut self) {
150 unsafe { LKRelease(self.native_room) }
151 }
152}
153
154struct RoomDelegate {
155 native_delegate: *const c_void,
156 weak_room: *const Room,
157}
158
159impl RoomDelegate {
160 fn new(weak_room: Weak<Room>) -> Self {
161 let weak_room = Weak::into_raw(weak_room);
162 let native_delegate = unsafe {
163 LKRoomDelegateCreate(
164 weak_room as *mut c_void,
165 Self::on_did_subscribe_to_remote_video_track,
166 )
167 };
168 Self {
169 native_delegate,
170 weak_room,
171 }
172 }
173
174 extern "C" fn on_did_subscribe_to_remote_video_track(room: *mut c_void, track: *const c_void) {
175 let room = unsafe { Weak::from_raw(room as *mut Room) };
176 let track = RemoteVideoTrack(track);
177 if let Some(room) = room.upgrade() {
178 room.did_subscribe_to_remote_video_track(track);
179 }
180 let _ = Weak::into_raw(room);
181 }
182}
183
184impl Drop for RoomDelegate {
185 fn drop(&mut self) {
186 unsafe {
187 LKRelease(self.native_delegate);
188 let _ = Weak::from_raw(self.weak_room);
189 }
190 }
191}
192
193pub struct LocalVideoTrack(*const c_void);
194
195impl LocalVideoTrack {
196 pub fn screen_share_for_window(window_id: u32) -> Self {
197 Self(unsafe { LKCreateScreenShareTrackForWindow(window_id) })
198 }
199}
200
201impl Drop for LocalVideoTrack {
202 fn drop(&mut self) {
203 unsafe { LKRelease(self.0) }
204 }
205}
206
207pub struct RemoteVideoTrack(*const c_void);
208
209impl RemoteVideoTrack {
210 pub fn add_renderer<F>(&self, callback: F)
211 where
212 F: 'static + FnMut(CVImageBuffer),
213 {
214 extern "C" fn on_frame<F>(callback_data: *mut c_void, frame: CVImageBufferRef)
215 where
216 F: FnMut(CVImageBuffer),
217 {
218 unsafe {
219 let buffer = CVImageBuffer::wrap_under_get_rule(frame);
220 let callback = &mut *(callback_data as *mut F);
221 callback(buffer);
222 }
223 }
224
225 extern "C" fn on_drop<F>(callback_data: *mut c_void) {
226 unsafe {
227 let _ = Box::from_raw(callback_data as *mut F);
228 }
229 }
230
231 let callback_data = Box::into_raw(Box::new(callback));
232 unsafe {
233 let renderer =
234 LKVideoRendererCreate(callback_data as *mut c_void, on_frame::<F>, on_drop::<F>);
235 LKVideoTrackAddRenderer(self.0, renderer);
236 }
237 }
238}
239
240impl Drop for RemoteVideoTrack {
241 fn drop(&mut self) {
242 unsafe { LKRelease(self.0) }
243 }
244}
245
246#[derive(Debug)]
247pub struct WindowInfo {
248 pub id: u32,
249 pub owner_pid: i32,
250 pub owner_name: Option<String>,
251}
252
253pub fn list_windows() -> Vec<WindowInfo> {
254 unsafe {
255 let dicts = CFArray::<CFDictionary>::wrap_under_get_rule(CGWindowListCopyWindowInfo(
256 kCGWindowListOptionOnScreenOnly | kCGWindowListOptionExcludeDesktopElements,
257 kCGNullWindowID,
258 ));
259
260 dicts
261 .iter()
262 .map(|dict| {
263 let id =
264 CFNumber::wrap_under_get_rule(*dict.get(kCGWindowNumber.as_void_ptr()) as _)
265 .to_i64()
266 .unwrap() as u32;
267
268 let owner_pid =
269 CFNumber::wrap_under_get_rule(*dict.get(kCGWindowOwnerPID.as_void_ptr()) as _)
270 .to_i32()
271 .unwrap();
272
273 let owner_name = dict
274 .find(kCGWindowOwnerName.as_void_ptr())
275 .map(|name| CFString::wrap_under_get_rule(*name as _).to_string());
276 WindowInfo {
277 id,
278 owner_pid,
279 owner_name,
280 }
281 })
282 .collect()
283 }
284}
285
286pub struct MacOSDisplay(*const c_void);
287
288pub fn display_sources() -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
289 extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) {
290 unsafe {
291 let tx = Box::from_raw(tx as *mut oneshot::Sender<Result<Vec<MacOSDisplay>>>);
292
293 if sources.is_null() {
294 let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error))));
295 } else {
296 let sources = CFArray::wrap_under_get_rule(sources);
297 let sources = sources
298 .into_iter()
299 .map(|source| MacOSDisplay(*source))
300 .collect();
301 let _ = tx.send(Ok(sources));
302 }
303 }
304 }
305
306 let (tx, rx) = oneshot::channel();
307
308 unsafe {
309 LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback);
310 }
311
312 async move { rx.await.unwrap() }
313}