main.rs

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