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}