Lukas WirthBump `ctor` (#57728)75c17a65 months agoHistory
344 lines · 12.4 KB · Rust
RawDownload
use crate::ns_string;use anyhow::{Result, anyhow};use block::ConcreteBlock;use cocoa::{    base::{YES, id, nil},    foundation::NSArray,};use collections::HashMap;use core_foundation::base::TCFType;use core_graphics::display::{    CGDirectDisplayID, CGDisplayCopyDisplayMode, CGDisplayModeGetPixelHeight,    CGDisplayModeGetPixelWidth, CGDisplayModeRelease,};use ctor::ctor;use futures::channel::oneshot;use gpui::{    DevicePixels, ForegroundExecutor, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream,    SharedString, SourceMetadata, size,};use media::core_media::{CMSampleBuffer, CMSampleBufferRef};use metal::NSInteger;use objc::{    class,    declare::ClassDecl,    msg_send,    runtime::{Class, Object, Sel},    sel, sel_impl,};use std::{cell::RefCell, ffi::c_void, mem, ptr, rc::Rc}; use crate::NSStringExt; #[derive(Clone)]pub struct MacScreenCaptureSource {    sc_display: id,    meta: Option<ScreenMeta>,} pub struct MacScreenCaptureStream {    sc_stream: id,    sc_stream_output: id,    meta: SourceMetadata,} static mut DELEGATE_CLASS: *const Class = ptr::null();static mut OUTPUT_CLASS: *const Class = ptr::null();const FRAME_CALLBACK_IVAR: &str = "frame_callback"; #[allow(non_upper_case_globals)]const SCStreamOutputTypeScreen: NSInteger = 0; impl ScreenCaptureSource for MacScreenCaptureSource {    fn metadata(&self) -> Result<SourceMetadata> {        let (display_id, size) = unsafe {            let display_id: CGDirectDisplayID = msg_send![self.sc_display, displayID];            let display_mode_ref = CGDisplayCopyDisplayMode(display_id);            let width = CGDisplayModeGetPixelWidth(display_mode_ref);            let height = CGDisplayModeGetPixelHeight(display_mode_ref);            CGDisplayModeRelease(display_mode_ref);             (                display_id,                size(DevicePixels(width as i32), DevicePixels(height as i32)),            )        };        let (label, is_main) = self            .meta            .clone()            .map(|meta| (meta.label, meta.is_main))            .unzip();         Ok(SourceMetadata {            id: display_id as u64,            label,            is_main,            resolution: size,        })    }     fn stream(        &self,        _foreground_executor: &ForegroundExecutor,        frame_callback: Box<dyn Fn(ScreenCaptureFrame) + Send>,    ) -> oneshot::Receiver<Result<Box<dyn ScreenCaptureStream>>> {        unsafe {            let stream: id = msg_send![class!(SCStream), alloc];            let filter: id = msg_send![class!(SCContentFilter), alloc];            let configuration: id = msg_send![class!(SCStreamConfiguration), alloc];            let delegate: id = msg_send![DELEGATE_CLASS, alloc];            let output: id = msg_send![OUTPUT_CLASS, alloc];             let excluded_windows = NSArray::array(nil);            let filter: id = msg_send![filter, initWithDisplay:self.sc_display excludingWindows:excluded_windows];            let configuration: id = msg_send![configuration, init];            let _: id = msg_send![configuration, setScalesToFit: true];            let _: id = msg_send![configuration, setPixelFormat: 0x42475241];            // let _: id = msg_send![configuration, setShowsCursor: false];            // let _: id = msg_send![configuration, setCaptureResolution: 3];            let delegate: id = msg_send![delegate, init];            let output: id = msg_send![output, init];             output.as_mut().unwrap().set_ivar(                FRAME_CALLBACK_IVAR,                Box::into_raw(Box::new(frame_callback)) as *mut c_void,            );             let meta = self.metadata().unwrap();            let _: id = msg_send![configuration, setWidth: meta.resolution.width.0 as i64];            let _: id = msg_send![configuration, setHeight: meta.resolution.height.0 as i64];            let stream: id = msg_send![stream, initWithFilter:filter configuration:configuration delegate:delegate];             // Stream contains filter, configuration, and delegate internally so we release them here            // to prevent a memory leak when steam is dropped            let _: () = msg_send![filter, release];            let _: () = msg_send![configuration, release];            let _: () = msg_send![delegate, release];             let (tx, rx) = oneshot::channel();             let mut error: id = nil;            let _: () = msg_send![stream, addStreamOutput:output type:SCStreamOutputTypeScreen sampleHandlerQueue:0 error:&mut error as *mut id];            if error != nil {                let message: id = msg_send![error, localizedDescription];                let _: () = msg_send![stream, release];                let _: () = msg_send![output, release];                tx.send(Err(anyhow!("failed to add stream output {message:?}")))                    .ok();                return rx;            }             let tx = Rc::new(RefCell::new(Some(tx)));            let handler = ConcreteBlock::new({                move |error: id| {                    let result = if error == nil {                        let stream = MacScreenCaptureStream {                            meta: meta.clone(),                            sc_stream: stream,                            sc_stream_output: output,                        };                        Ok(Box::new(stream) as Box<dyn ScreenCaptureStream>)                    } else {                        let _: () = msg_send![stream, release];                        let _: () = msg_send![output, release];                        let message: id = msg_send![error, localizedDescription];                        Err(anyhow!("failed to start screen capture stream {message:?}"))                    };                    if let Some(tx) = tx.borrow_mut().take() {                        tx.send(result).ok();                    }                }            });            let handler = handler.copy();            let _: () = msg_send![stream, startCaptureWithCompletionHandler:handler];            rx        }    }} impl Drop for MacScreenCaptureSource {    fn drop(&mut self) {        unsafe {            let _: () = msg_send![self.sc_display, release];        }    }} impl ScreenCaptureStream for MacScreenCaptureStream {    fn metadata(&self) -> Result<SourceMetadata> {        Ok(self.meta.clone())    }} impl Drop for MacScreenCaptureStream {    fn drop(&mut self) {        unsafe {            let mut error: id = nil;            let _: () = msg_send![self.sc_stream, removeStreamOutput:self.sc_stream_output type:SCStreamOutputTypeScreen error:&mut error as *mut _];            if error != nil {                let message: id = msg_send![error, localizedDescription];                log::error!("failed to add stream  output {message:?}");            }             let handler = ConcreteBlock::new(move |error: id| {                if error != nil {                    let message: id = msg_send![error, localizedDescription];                    log::error!("failed to stop screen capture stream {message:?}");                }            });            let block = handler.copy();            let _: () = msg_send![self.sc_stream, stopCaptureWithCompletionHandler:block];            let _: () = msg_send![self.sc_stream, release];            let _: () = msg_send![self.sc_stream_output, release];        }    }} #[derive(Clone)]struct ScreenMeta {    label: SharedString,    // Is this the screen with menu bar?    is_main: bool,} unsafe fn screen_id_to_human_label() -> HashMap<CGDirectDisplayID, ScreenMeta> {    let screens: id = msg_send![class!(NSScreen), screens];    let count: usize = msg_send![screens, count];    let mut map = HashMap::default();    let screen_number_key = unsafe { ns_string("NSScreenNumber") };    for i in 0..count {        let screen: id = msg_send![screens, objectAtIndex: i];        let device_desc: id = msg_send![screen, deviceDescription];        if device_desc == nil {            continue;        }         let nsnumber: id = msg_send![device_desc, objectForKey: screen_number_key];        if nsnumber == nil {            continue;        }         let screen_id: u32 = msg_send![nsnumber, unsignedIntValue];         let name: id = msg_send![screen, localizedName];        if name != nil {            let cstr: *const std::os::raw::c_char = msg_send![name, UTF8String];            let rust_str = unsafe {                std::ffi::CStr::from_ptr(cstr)                    .to_string_lossy()                    .into_owned()            };            map.insert(                screen_id,                ScreenMeta {                    label: rust_str.into(),                    is_main: i == 0,                },            );        }    }    map} pub(crate) fn get_sources() -> oneshot::Receiver<Result<Vec<Rc<dyn ScreenCaptureSource>>>> {    unsafe {        let (tx, rx) = oneshot::channel();        let tx = Rc::new(RefCell::new(Some(tx)));        let screen_id_to_label = screen_id_to_human_label();        let block = ConcreteBlock::new(move |shareable_content: id, error: id| {            let Some(tx) = tx.borrow_mut().take() else {                return;            };             let result = if error == nil {                let displays: id = msg_send![shareable_content, displays];                let mut result = Vec::new();                for i in 0..displays.count() {                    let display = displays.objectAtIndex(i);                    let id: CGDirectDisplayID = msg_send![display, displayID];                    let meta = screen_id_to_label.get(&id).cloned();                    let source = MacScreenCaptureSource {                        sc_display: msg_send![display, retain],                        meta,                    };                    result.push(Rc::new(source) as Rc<dyn ScreenCaptureSource>);                }                Ok(result)            } else {                let msg: id = msg_send![error, localizedDescription];                Err(anyhow!(                    "Screen share failed: {:?}",                    NSStringExt::to_str(&msg)                ))            };            tx.send(result).ok();        });        let block = block.copy();         let _: () = msg_send![            class!(SCShareableContent),            getShareableContentExcludingDesktopWindows:YES                                   onScreenWindowsOnly:YES                                     completionHandler:block];        rx    }} #[ctor(unsafe)]unsafe fn build_classes() {    let mut decl = ClassDecl::new("GPUIStreamDelegate", class!(NSObject)).unwrap();    unsafe {        decl.add_method(            sel!(outputVideoEffectDidStartForStream:),            output_video_effect_did_start_for_stream as extern "C" fn(&Object, Sel, id),        );        decl.add_method(            sel!(outputVideoEffectDidStopForStream:),            output_video_effect_did_stop_for_stream as extern "C" fn(&Object, Sel, id),        );        decl.add_method(            sel!(stream:didStopWithError:),            stream_did_stop_with_error as extern "C" fn(&Object, Sel, id, id),        );        DELEGATE_CLASS = decl.register();         let mut decl = ClassDecl::new("GPUIStreamOutput", class!(NSObject)).unwrap();        decl.add_method(            sel!(stream:didOutputSampleBuffer:ofType:),            stream_did_output_sample_buffer_of_type                as extern "C" fn(&Object, Sel, id, id, NSInteger),        );        decl.add_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR);         OUTPUT_CLASS = decl.register();    }} extern "C" fn output_video_effect_did_start_for_stream(_this: &Object, _: Sel, _stream: id) {} extern "C" fn output_video_effect_did_stop_for_stream(_this: &Object, _: Sel, _stream: id) {} extern "C" fn stream_did_stop_with_error(_this: &Object, _: Sel, _stream: id, _error: id) {} extern "C" fn stream_did_output_sample_buffer_of_type(    this: &Object,    _: Sel,    _stream: id,    sample_buffer: id,    buffer_type: NSInteger,) {    if buffer_type != SCStreamOutputTypeScreen {        return;    }     unsafe {        let sample_buffer = sample_buffer as CMSampleBufferRef;        let sample_buffer = CMSampleBuffer::wrap_under_get_rule(sample_buffer);        if let Some(buffer) = sample_buffer.image_buffer() {            let callback: Box<Box<dyn Fn(ScreenCaptureFrame)>> =                Box::from_raw(*this.get_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR) as *mut _);            callback(ScreenCaptureFrame(buffer));            mem::forget(callback);        }    }}