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}