screen_capture.rs

  1use crate::{
  2    DevicePixels, ForegroundExecutor, Size,
  3    platform::{ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream},
  4    size,
  5};
  6use anyhow::{Result, anyhow};
  7use block::ConcreteBlock;
  8use cocoa::{
  9    base::{YES, id, nil},
 10    foundation::NSArray,
 11};
 12use core_foundation::base::TCFType;
 13use core_graphics::display::{
 14    CGDirectDisplayID, CGDisplayCopyDisplayMode, CGDisplayModeGetPixelHeight,
 15    CGDisplayModeGetPixelWidth, CGDisplayModeRelease,
 16};
 17use ctor::ctor;
 18use futures::channel::oneshot;
 19use media::core_media::{CMSampleBuffer, CMSampleBufferRef};
 20use metal::NSInteger;
 21use objc::{
 22    class,
 23    declare::ClassDecl,
 24    msg_send,
 25    runtime::{Class, Object, Sel},
 26    sel, sel_impl,
 27};
 28use std::{cell::RefCell, ffi::c_void, mem, ptr, rc::Rc};
 29
 30use super::NSStringExt;
 31
 32#[derive(Clone)]
 33pub struct MacScreenCaptureSource {
 34    sc_display: id,
 35}
 36
 37pub struct MacScreenCaptureStream {
 38    sc_stream: id,
 39    sc_stream_output: id,
 40}
 41
 42static mut DELEGATE_CLASS: *const Class = ptr::null();
 43static mut OUTPUT_CLASS: *const Class = ptr::null();
 44const FRAME_CALLBACK_IVAR: &str = "frame_callback";
 45
 46#[allow(non_upper_case_globals)]
 47const SCStreamOutputTypeScreen: NSInteger = 0;
 48
 49impl ScreenCaptureSource for MacScreenCaptureSource {
 50    fn resolution(&self) -> Result<Size<DevicePixels>> {
 51        unsafe {
 52            let display_id: CGDirectDisplayID = msg_send![self.sc_display, displayID];
 53            let display_mode_ref = CGDisplayCopyDisplayMode(display_id);
 54            let width = CGDisplayModeGetPixelWidth(display_mode_ref);
 55            let height = CGDisplayModeGetPixelHeight(display_mode_ref);
 56            CGDisplayModeRelease(display_mode_ref);
 57
 58            Ok(size(
 59                DevicePixels(width as i32),
 60                DevicePixels(height as i32),
 61            ))
 62        }
 63    }
 64
 65    fn stream(
 66        &self,
 67        _foreground_executor: &ForegroundExecutor,
 68        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,
 69    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {
 70        unsafe {
 71            let stream: id = msg_send![class!(SCStream), alloc];
 72            let filter: id = msg_send![class!(SCContentFilter), alloc];
 73            let configuration: id = msg_send![class!(SCStreamConfiguration), alloc];
 74            let delegate: id = msg_send![DELEGATE_CLASS, alloc];
 75            let output: id = msg_send![OUTPUT_CLASS, alloc];
 76
 77            let excluded_windows = NSArray::array(nil);
 78            let filter: id = msg_send![filter, initWithDisplay:self.sc_display excludingWindows:excluded_windows];
 79            let configuration: id = msg_send![configuration, init];
 80            let _: id = msg_send![configuration, setScalesToFit: true];
 81            let _: id = msg_send![configuration, setPixelFormat: 0x42475241];
 82            // let _: id = msg_send![configuration, setShowsCursor: false];
 83            // let _: id = msg_send![configuration, setCaptureResolution: 3];
 84            let delegate: id = msg_send![delegate, init];
 85            let output: id = msg_send![output, init];
 86
 87            output.as_mut().unwrap().set_ivar(
 88                FRAME_CALLBACK_IVAR,
 89                Box::into_raw(Box::new(frame_callback)) as *mut c_void,
 90            );
 91
 92            let resolution = self.resolution().unwrap();
 93            let _: id = msg_send![configuration, setWidth: resolution.width.0 as i64];
 94            let _: id = msg_send![configuration, setHeight: resolution.height.0 as i64];
 95            let stream: id = msg_send![stream, initWithFilter:filter configuration:configuration delegate:delegate];
 96
 97            let (mut tx, rx) = oneshot::channel();
 98
 99            let mut error: id = nil;
100            let _: () = msg_send![stream, addStreamOutput:output type:SCStreamOutputTypeScreen sampleHandlerQueue:0 error:&mut error as *mut id];
101            if error != nil {
102                let message: id = msg_send![error, localizedDescription];
103                tx.send(Err(anyhow!("failed to add stream  output {message:?}")))
104                    .ok();
105                return rx;
106            }
107
108            let tx = Rc::new(RefCell::new(Some(tx)));
109            let handler = ConcreteBlock::new({
110                move |error: id| {
111                    let result = if error == nil {
112                        let stream = MacScreenCaptureStream {
113                            sc_stream: stream,
114                            sc_stream_output: output,
115                        };
116                        Ok(Box::new(stream) as Box<dyn ScreenCaptureStream>)
117                    } else {
118                        let message: id = msg_send![error, localizedDescription];
119                        Err(anyhow!("failed to stop screen capture stream {message:?}"))
120                    };
121                    if let Some(tx) = tx.borrow_mut().take() {
122                        tx.send(result).ok();
123                    }
124                }
125            });
126            let handler = handler.copy();
127            let _: () = msg_send![stream, startCaptureWithCompletionHandler:handler];
128            rx
129        }
130    }
131}
132
133impl Drop for MacScreenCaptureSource {
134    fn drop(&mut self) {
135        unsafe {
136            let _: () = msg_send![self.sc_display, release];
137        }
138    }
139}
140
141impl ScreenCaptureStream for MacScreenCaptureStream {}
142
143impl Drop for MacScreenCaptureStream {
144    fn drop(&mut self) {
145        unsafe {
146            let mut error: id = nil;
147            let _: () = msg_send![self.sc_stream, removeStreamOutput:self.sc_stream_output type:SCStreamOutputTypeScreen error:&mut error as *mut _];
148            if error != nil {
149                let message: id = msg_send![error, localizedDescription];
150                log::error!("failed to add stream  output {message:?}");
151            }
152
153            let handler = ConcreteBlock::new(move |error: id| {
154                if error != nil {
155                    let message: id = msg_send![error, localizedDescription];
156                    log::error!("failed to stop screen capture stream {message:?}");
157                }
158            });
159            let block = handler.copy();
160            let _: () = msg_send![self.sc_stream, stopCaptureWithCompletionHandler:block];
161            let _: () = msg_send![self.sc_stream, release];
162            let _: () = msg_send![self.sc_stream_output, release];
163        }
164    }
165}
166
167pub(crate) fn get_sources() -> oneshot::Receiver<Result<Vec<Box<dyn ScreenCaptureSource>>>> {
168    unsafe {
169        let (mut tx, rx) = oneshot::channel();
170        let tx = Rc::new(RefCell::new(Some(tx)));
171
172        let block = ConcreteBlock::new(move |shareable_content: id, error: id| {
173            let Some(mut tx) = tx.borrow_mut().take() else {
174                return;
175            };
176            let result = if error == nil {
177                let displays: id = msg_send![shareable_content, displays];
178                let mut result = Vec::new();
179                for i in 0..displays.count() {
180                    let display = displays.objectAtIndex(i);
181                    let source = MacScreenCaptureSource {
182                        sc_display: msg_send![display, retain],
183                    };
184                    result.push(Box::new(source) as Box<dyn ScreenCaptureSource>);
185                }
186                Ok(result)
187            } else {
188                let msg: id = msg_send![error, localizedDescription];
189                Err(anyhow!(
190                    "Screen share failed: {:?}",
191                    NSStringExt::to_str(&msg)
192                ))
193            };
194            tx.send(result).ok();
195        });
196        let block = block.copy();
197
198        let _: () = msg_send![
199            class!(SCShareableContent),
200            getShareableContentExcludingDesktopWindows:YES
201                                   onScreenWindowsOnly:YES
202                                     completionHandler:block];
203        rx
204    }
205}
206
207#[ctor]
208unsafe fn build_classes() {
209    let mut decl = ClassDecl::new("GPUIStreamDelegate", class!(NSObject)).unwrap();
210    unsafe {
211        decl.add_method(
212            sel!(outputVideoEffectDidStartForStream:),
213            output_video_effect_did_start_for_stream as extern "C" fn(&Object, Sel, id),
214        );
215        decl.add_method(
216            sel!(outputVideoEffectDidStopForStream:),
217            output_video_effect_did_stop_for_stream as extern "C" fn(&Object, Sel, id),
218        );
219        decl.add_method(
220            sel!(stream:didStopWithError:),
221            stream_did_stop_with_error as extern "C" fn(&Object, Sel, id, id),
222        );
223        DELEGATE_CLASS = decl.register();
224
225        let mut decl = ClassDecl::new("GPUIStreamOutput", class!(NSObject)).unwrap();
226        decl.add_method(
227            sel!(stream:didOutputSampleBuffer:ofType:),
228            stream_did_output_sample_buffer_of_type
229                as extern "C" fn(&Object, Sel, id, id, NSInteger),
230        );
231        decl.add_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR);
232
233        OUTPUT_CLASS = decl.register();
234    }
235}
236
237extern "C" fn output_video_effect_did_start_for_stream(_this: &Object, _: Sel, _stream: id) {}
238
239extern "C" fn output_video_effect_did_stop_for_stream(_this: &Object, _: Sel, _stream: id) {}
240
241extern "C" fn stream_did_stop_with_error(_this: &Object, _: Sel, _stream: id, _error: id) {}
242
243extern "C" fn stream_did_output_sample_buffer_of_type(
244    this: &Object,
245    _: Sel,
246    _stream: id,
247    sample_buffer: id,
248    buffer_type: NSInteger,
249) {
250    if buffer_type != SCStreamOutputTypeScreen {
251        return;
252    }
253
254    unsafe {
255        let sample_buffer = sample_buffer as CMSampleBufferRef;
256        let sample_buffer = CMSampleBuffer::wrap_under_get_rule(sample_buffer);
257        if let Some(buffer) = sample_buffer.image_buffer() {
258            let callback: Box<Box<dyn Fn(ScreenCaptureFrame)>> =
259                Box::from_raw(*this.get_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR) as *mut _);
260            callback(ScreenCaptureFrame(buffer));
261            mem::forget(callback);
262        }
263    }
264}