main.rs

  1mod bindings;
  2
  3use crate::bindings::{dispatch_queue_create, NSObject, SCStreamOutputType};
  4use block::ConcreteBlock;
  5use cocoa::{
  6    base::{id, nil, YES},
  7    foundation::{NSArray, NSString, NSUInteger},
  8};
  9use core_foundation::{base::TCFType, number::CFNumberRef, string::CFStringRef};
 10use core_media::{CMSampleBuffer, CMSampleBufferRef};
 11use gpui::{actions, elements::*, keymap::Binding, Menu, MenuItem};
 12use log::LevelFilter;
 13use objc::{
 14    class,
 15    declare::ClassDecl,
 16    msg_send,
 17    runtime::{Object, Sel},
 18    sel, sel_impl,
 19};
 20use simplelog::SimpleLogger;
 21use std::{ptr, slice, str};
 22
 23#[allow(non_upper_case_globals)]
 24const NSUTF8StringEncoding: NSUInteger = 4;
 25
 26actions!(capture, [Quit]);
 27
 28fn main() {
 29    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 30
 31    gpui::App::new(()).unwrap().run(|cx| {
 32        cx.platform().activate(true);
 33        cx.add_global_action(quit);
 34
 35        cx.add_bindings([Binding::new("cmd-q", Quit, None)]);
 36        cx.set_menus(vec![Menu {
 37            name: "Zed",
 38            items: vec![MenuItem::Action {
 39                name: "Quit",
 40                action: Box::new(Quit),
 41            }],
 42        }]);
 43
 44        unsafe {
 45            let block = ConcreteBlock::new(move |content: id, error: id| {
 46                if !error.is_null() {
 47                    println!("ERROR {}", string_from_objc(msg_send![error, localizedDescription]));
 48                    return;
 49                }
 50
 51                let applications: id = msg_send![content, applications];
 52                let displays: id = msg_send![content, displays];
 53                let display: id = displays.objectAtIndex(0);
 54                let display_width: usize = msg_send![display, width];
 55                let display_height: usize = msg_send![display, height];
 56
 57                let mut decl = ClassDecl::new("CaptureOutput", class!(NSObject)).unwrap();
 58                decl.add_method(sel!(stream:didOutputSampleBuffer:ofType:), sample_output as extern "C" fn(&Object, Sel, id, id, SCStreamOutputType));
 59                let capture_output_class = decl.register();
 60
 61                let output: id = msg_send![capture_output_class, alloc];
 62                let output: id = msg_send![output, init];
 63
 64                let filter: id = msg_send![class!(SCContentFilter), alloc];
 65                let filter: id = msg_send![filter, initWithDisplay: display includingApplications: applications exceptingWindows: nil];
 66                // let filter: id = msg_send![filter, initWithDesktopIndependentWindow: window];
 67                let config: id = msg_send![class!(SCStreamConfiguration), alloc];
 68                let config: id = msg_send![config, init];
 69                let _: () = msg_send![config, setWidth: display_width];
 70                let _: () = msg_send![config, setHeight: display_height];
 71                let _: () = msg_send![config, setMinimumFrameInterval: bindings::CMTimeMake(1, 60)];
 72                let _: () = msg_send![config, setQueueDepth: 6];
 73                let _: () = msg_send![config, setShowsCursor: YES];
 74
 75                let stream: id = msg_send![class!(SCStream), alloc];
 76                let stream: id = msg_send![stream, initWithFilter: filter configuration: config delegate: output];
 77                let error: id = nil;
 78                let queue = dispatch_queue_create(ptr::null(), NSObject(ptr::null_mut()));
 79
 80                let _: () = msg_send![stream,
 81                    addStreamOutput: output type: bindings::SCStreamOutputType_SCStreamOutputTypeScreen
 82                    sampleHandlerQueue: queue
 83                    error: &error
 84                ];
 85
 86                let start_capture_completion = ConcreteBlock::new(move |error: id| {
 87                    if !error.is_null() {
 88                        println!("error starting capture... error? {}", string_from_objc(msg_send![error, localizedDescription]));
 89                        return;
 90                    }
 91
 92                    println!("starting capture");
 93                });
 94
 95                assert!(!stream.is_null());
 96                let _: () = msg_send![stream, startCaptureWithCompletionHandler: start_capture_completion];
 97
 98            });
 99
100            let _: id = msg_send![
101                class!(SCShareableContent),
102                getShareableContentWithCompletionHandler: block
103            ];
104        }
105
106        // cx.add_window(Default::default(), |_| ScreenCaptureView);
107    });
108}
109
110struct ScreenCaptureView;
111
112impl gpui::Entity for ScreenCaptureView {
113    type Event = ();
114}
115
116impl gpui::View for ScreenCaptureView {
117    fn ui_name() -> &'static str {
118        "View"
119    }
120
121    fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
122        Empty::new().boxed()
123    }
124}
125
126pub unsafe fn string_from_objc(string: id) -> String {
127    if string.is_null() {
128        Default::default()
129    } else {
130        let len = msg_send![string, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
131        let bytes = string.UTF8String() as *const u8;
132        str::from_utf8(slice::from_raw_parts(bytes, len))
133            .unwrap()
134            .to_string()
135    }
136}
137
138extern "C" fn sample_output(
139    _: &Object,
140    _: Sel,
141    _stream: id,
142    buffer: id,
143    _kind: SCStreamOutputType,
144) {
145    let buffer = unsafe { CMSampleBuffer::wrap_under_get_rule(buffer as CMSampleBufferRef) };
146    let attachments = buffer.attachments();
147    let attachments = attachments.first().expect("no attachments for sample");
148
149    unsafe {
150        let string = bindings::SCStreamFrameInfoStatus.0 as CFStringRef;
151        let status = core_foundation::number::CFNumber::wrap_under_get_rule(
152            *attachments.get(string) as CFNumberRef,
153        )
154        .to_i64()
155        .expect("invalid frame info status");
156
157        if status != bindings::SCFrameStatus_SCFrameStatusComplete {
158            println!("received incomplete frame");
159            return;
160        }
161    }
162
163    let image_buffer = buffer.image_buffer();
164    dbg!(image_buffer.width(), image_buffer.height());
165    let io_surface = image_buffer.io_surface();
166}
167
168fn quit(_: &Quit, cx: &mut gpui::MutableAppContext) {
169    cx.platform().quit();
170}
171
172mod core_media {
173    #![allow(non_snake_case)]
174
175    use crate::core_video::{CVImageBuffer, CVImageBufferRef};
176    use core_foundation::{
177        array::{CFArray, CFArrayRef},
178        base::{CFTypeID, TCFType},
179        declare_TCFType,
180        dictionary::CFDictionary,
181        impl_CFTypeDescription, impl_TCFType,
182        string::CFString,
183    };
184    use std::ffi::c_void;
185
186    #[repr(C)]
187    pub struct __CMSampleBuffer(c_void);
188    // The ref type must be a pointer to the underlying struct.
189    pub type CMSampleBufferRef = *const __CMSampleBuffer;
190
191    declare_TCFType!(CMSampleBuffer, CMSampleBufferRef);
192    impl_TCFType!(CMSampleBuffer, CMSampleBufferRef, CMSampleBufferGetTypeID);
193    impl_CFTypeDescription!(CMSampleBuffer);
194
195    impl CMSampleBuffer {
196        pub fn attachments(&self) -> Vec<CFDictionary<CFString>> {
197            unsafe {
198                let attachments =
199                    CMSampleBufferGetSampleAttachmentsArray(self.as_concrete_TypeRef(), true);
200                CFArray::<CFDictionary>::wrap_under_get_rule(attachments)
201                    .into_iter()
202                    .map(|attachments| {
203                        CFDictionary::wrap_under_get_rule(attachments.as_concrete_TypeRef())
204                    })
205                    .collect()
206            }
207        }
208
209        pub fn image_buffer(&self) -> CVImageBuffer {
210            unsafe {
211                CVImageBuffer::wrap_under_get_rule(CMSampleBufferGetImageBuffer(
212                    self.as_concrete_TypeRef(),
213                ))
214            }
215        }
216    }
217
218    extern "C" {
219        fn CMSampleBufferGetTypeID() -> CFTypeID;
220        fn CMSampleBufferGetSampleAttachmentsArray(
221            buffer: CMSampleBufferRef,
222            create_if_necessary: bool,
223        ) -> CFArrayRef;
224        fn CMSampleBufferGetImageBuffer(buffer: CMSampleBufferRef) -> CVImageBufferRef;
225    }
226}
227
228mod core_video {
229    #![allow(non_snake_case)]
230
231    use crate::io_surface::{IOSurface, IOSurfaceRef};
232    use core_foundation::{
233        base::{CFTypeID, TCFType},
234        declare_TCFType, impl_CFTypeDescription, impl_TCFType,
235    };
236    use std::ffi::c_void;
237
238    #[repr(C)]
239    pub struct __CVImageBuffer(c_void);
240    // The ref type must be a pointer to the underlying struct.
241    pub type CVImageBufferRef = *const __CVImageBuffer;
242
243    declare_TCFType!(CVImageBuffer, CVImageBufferRef);
244    impl_TCFType!(CVImageBuffer, CVImageBufferRef, CVImageBufferGetTypeID);
245    impl_CFTypeDescription!(CVImageBuffer);
246
247    impl CVImageBuffer {
248        pub fn io_surface(&self) -> IOSurface {
249            unsafe {
250                IOSurface::wrap_under_get_rule(CVPixelBufferGetIOSurface(
251                    self.as_concrete_TypeRef(),
252                ))
253            }
254        }
255
256        pub fn width(&self) -> usize {
257            unsafe { CVPixelBufferGetWidth(self.as_concrete_TypeRef()) }
258        }
259
260        pub fn height(&self) -> usize {
261            unsafe { CVPixelBufferGetHeight(self.as_concrete_TypeRef()) }
262        }
263    }
264
265    extern "C" {
266        fn CVImageBufferGetTypeID() -> CFTypeID;
267        fn CVPixelBufferGetIOSurface(buffer: CVImageBufferRef) -> IOSurfaceRef;
268        fn CVPixelBufferGetWidth(buffer: CVImageBufferRef) -> usize;
269        fn CVPixelBufferGetHeight(buffer: CVImageBufferRef) -> usize;
270    }
271}
272
273mod io_surface {
274    #![allow(non_snake_case)]
275
276    use core_foundation::{
277        base::{CFTypeID, TCFType},
278        declare_TCFType, impl_CFTypeDescription, impl_TCFType,
279    };
280    use std::ffi::c_void;
281
282    #[repr(C)]
283    pub struct __IOSurface(c_void);
284    // The ref type must be a pointer to the underlying struct.
285    pub type IOSurfaceRef = *const __IOSurface;
286
287    declare_TCFType!(IOSurface, IOSurfaceRef);
288    impl_TCFType!(IOSurface, IOSurfaceRef, IOSurfaceGetTypeID);
289    impl_CFTypeDescription!(IOSurface);
290
291    extern "C" {
292        fn IOSurfaceGetTypeID() -> CFTypeID;
293    }
294}