1use anyhow::{anyhow, Context, Result};
2use core_foundation::{
3 array::CFArray,
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}
60
61pub struct Room {
62 native_room: *const c_void,
63 remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<Arc<RemoteVideoTrack>>>>,
64 _delegate: RoomDelegate,
65}
66
67impl Room {
68 pub fn new() -> Arc<Self> {
69 Arc::new_cyclic(|weak_room| {
70 let delegate = RoomDelegate::new(weak_room.clone());
71 Self {
72 native_room: unsafe { LKRoomCreate(delegate.native_delegate) },
73 remote_video_track_subscribers: Default::default(),
74 _delegate: delegate,
75 }
76 })
77 }
78
79 pub fn connect(&self, url: &str, token: &str) -> impl Future<Output = Result<()>> {
80 let url = CFString::new(url);
81 let token = CFString::new(token);
82 let (did_connect, tx, rx) = Self::build_done_callback();
83 unsafe {
84 LKRoomConnect(
85 self.native_room,
86 url.as_concrete_TypeRef(),
87 token.as_concrete_TypeRef(),
88 did_connect,
89 tx,
90 )
91 }
92
93 async { rx.await.unwrap().context("error connecting to room") }
94 }
95
96 pub fn publish_video_track(&self, track: &LocalVideoTrack) -> impl Future<Output = Result<()>> {
97 let (did_publish, tx, rx) = Self::build_done_callback();
98 unsafe {
99 LKRoomPublishVideoTrack(self.native_room, track.0, did_publish, tx);
100 }
101 async { rx.await.unwrap().context("error publishing video track") }
102 }
103
104 pub fn remote_video_tracks(&self) -> mpsc::UnboundedReceiver<Arc<RemoteVideoTrack>> {
105 let (tx, rx) = mpsc::unbounded();
106 self.remote_video_track_subscribers.lock().push(tx);
107 rx
108 }
109
110 fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) {
111 let track = Arc::new(track);
112 self.remote_video_track_subscribers
113 .lock()
114 .retain(|tx| tx.unbounded_send(track.clone()).is_ok());
115 }
116
117 fn build_done_callback() -> (
118 extern "C" fn(*mut c_void, CFStringRef),
119 *mut c_void,
120 oneshot::Receiver<Result<()>>,
121 ) {
122 let (tx, rx) = oneshot::channel();
123 extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) {
124 let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<()>>) };
125 if error.is_null() {
126 let _ = tx.send(Ok(()));
127 } else {
128 let error = unsafe { CFString::wrap_under_get_rule(error).to_string() };
129 let _ = tx.send(Err(anyhow!(error)));
130 }
131 }
132 (
133 done_callback,
134 Box::into_raw(Box::new(tx)) as *mut c_void,
135 rx,
136 )
137 }
138}
139
140impl Drop for Room {
141 fn drop(&mut self) {
142 unsafe { LKRelease(self.native_room) }
143 }
144}
145
146struct RoomDelegate {
147 native_delegate: *const c_void,
148 weak_room: *const Room,
149}
150
151impl RoomDelegate {
152 fn new(weak_room: Weak<Room>) -> Self {
153 let weak_room = Weak::into_raw(weak_room);
154 let native_delegate = unsafe {
155 LKRoomDelegateCreate(
156 weak_room as *mut c_void,
157 Self::on_did_subscribe_to_remote_video_track,
158 )
159 };
160 Self {
161 native_delegate,
162 weak_room,
163 }
164 }
165
166 extern "C" fn on_did_subscribe_to_remote_video_track(room: *mut c_void, track: *const c_void) {
167 let room = unsafe { Weak::from_raw(room as *mut Room) };
168 let track = RemoteVideoTrack(track);
169 if let Some(room) = room.upgrade() {
170 room.did_subscribe_to_remote_video_track(track);
171 }
172 let _ = Weak::into_raw(room);
173 }
174}
175
176impl Drop for RoomDelegate {
177 fn drop(&mut self) {
178 unsafe {
179 LKRelease(self.native_delegate);
180 let _ = Weak::from_raw(self.weak_room);
181 }
182 }
183}
184
185pub struct LocalVideoTrack(*const c_void);
186
187impl LocalVideoTrack {
188 pub fn screen_share_for_window(window_id: u32) -> Self {
189 Self(unsafe { LKCreateScreenShareTrackForWindow(window_id) })
190 }
191}
192
193impl Drop for LocalVideoTrack {
194 fn drop(&mut self) {
195 unsafe { LKRelease(self.0) }
196 }
197}
198
199pub struct RemoteVideoTrack(*const c_void);
200
201impl RemoteVideoTrack {
202 pub fn add_renderer<F>(&self, callback: F)
203 where
204 F: 'static + FnMut(CVImageBuffer),
205 {
206 extern "C" fn on_frame<F>(callback_data: *mut c_void, frame: CVImageBufferRef)
207 where
208 F: FnMut(CVImageBuffer),
209 {
210 unsafe {
211 let buffer = CVImageBuffer::wrap_under_get_rule(frame);
212 let callback = &mut *(callback_data as *mut F);
213 callback(buffer);
214 }
215 }
216
217 extern "C" fn on_drop<F>(callback_data: *mut c_void) {
218 unsafe {
219 let _ = Box::from_raw(callback_data as *mut F);
220 }
221 }
222
223 let callback_data = Box::into_raw(Box::new(callback));
224 unsafe {
225 let renderer =
226 LKVideoRendererCreate(callback_data as *mut c_void, on_frame::<F>, on_drop::<F>);
227 LKVideoTrackAddRenderer(self.0, renderer);
228 }
229 }
230}
231
232impl Drop for RemoteVideoTrack {
233 fn drop(&mut self) {
234 unsafe { LKRelease(self.0) }
235 }
236}
237
238#[derive(Debug)]
239pub struct WindowInfo {
240 pub id: u32,
241 pub owner_pid: i32,
242 pub owner_name: Option<String>,
243}
244
245pub fn list_windows() -> Vec<WindowInfo> {
246 unsafe {
247 let dicts = CFArray::<CFDictionary>::wrap_under_get_rule(CGWindowListCopyWindowInfo(
248 kCGWindowListOptionOnScreenOnly | kCGWindowListOptionExcludeDesktopElements,
249 kCGNullWindowID,
250 ));
251
252 dicts
253 .iter()
254 .map(|dict| {
255 let id =
256 CFNumber::wrap_under_get_rule(*dict.get(kCGWindowNumber.as_void_ptr()) as _)
257 .to_i64()
258 .unwrap() as u32;
259
260 let owner_pid =
261 CFNumber::wrap_under_get_rule(*dict.get(kCGWindowOwnerPID.as_void_ptr()) as _)
262 .to_i32()
263 .unwrap();
264
265 let owner_name = dict
266 .find(kCGWindowOwnerName.as_void_ptr())
267 .map(|name| CFString::wrap_under_get_rule(*name as _).to_string());
268 WindowInfo {
269 id,
270 owner_pid,
271 owner_name,
272 }
273 })
274 .collect()
275 }
276}