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}