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, } 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 { 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, ) -> oneshot::Receiver>> { 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) } 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 { 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 { 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>>> { 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); } 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::from_raw(*this.get_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR) as *mut _); callback(ScreenCaptureFrame(buffer)); mem::forget(callback); } } }