screencapturekit/shareable_content/
snapshot.rs1#![allow(
11 clippy::cast_possible_wrap,
12 clippy::cast_sign_loss,
13 clippy::cast_possible_truncation
14)]
15
16use crate::cg::CGRect;
17use crate::ffi::{FFIApplicationData, FFIDisplayData, FFIWindowData};
18use std::ffi::c_void;
19use std::mem::MaybeUninit;
20
21const MAX_DISPLAYS: usize = 64;
26const MAX_WINDOWS: usize = 4096;
27const MAX_APPS: usize = 1024;
28const STRING_POOL_BYTES: usize = 256 * 1024;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct DisplaySnapshot {
33 pub display_id: u32,
34 pub width: i32,
35 pub height: i32,
36 pub frame: CGRect,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct ApplicationSnapshot {
42 pub process_id: i32,
43 pub bundle_identifier: String,
44 pub application_name: String,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct WindowSnapshot {
50 pub window_id: u32,
51 pub window_layer: i32,
52 pub is_on_screen: bool,
53 pub is_active: bool,
54 pub frame: CGRect,
55 pub title: Option<String>,
56 pub owning_app_index: Option<usize>,
60}
61
62#[derive(Debug, Default, Clone, PartialEq, Eq)]
64pub struct ContentSnapshot {
65 pub displays: Vec<DisplaySnapshot>,
66 pub applications: Vec<ApplicationSnapshot>,
67 pub windows: Vec<WindowSnapshot>,
68}
69
70impl ContentSnapshot {
71 pub(crate) fn collect(content: *const c_void) -> Option<Self> {
74 if content.is_null() {
75 return None;
76 }
77
78 let displays = unsafe { collect_displays(content) };
83 let applications = unsafe { collect_applications(content) };
84 let windows = unsafe { collect_windows(content, applications.len()) };
85
86 Some(Self {
87 displays,
88 applications,
89 windows,
90 })
91 }
92}
93
94unsafe fn collect_displays(content: *const c_void) -> Vec<DisplaySnapshot> {
95 unsafe {
96 let mut buffer: Vec<MaybeUninit<FFIDisplayData>> = Vec::with_capacity(MAX_DISPLAYS);
101 let written = crate::ffi::sc_shareable_content_get_displays_batch(
102 content,
103 buffer.as_mut_ptr().cast::<c_void>(),
104 MAX_DISPLAYS as isize,
105 );
106 if written <= 0 {
107 return Vec::new();
108 }
109 let count = (written as usize).min(MAX_DISPLAYS);
110
111 (0..count)
112 .map(|i| {
113 let d = buffer[i].assume_init();
114 DisplaySnapshot {
115 display_id: d.display_id,
116 width: d.width,
117 height: d.height,
118 frame: CGRect::new(d.frame.x, d.frame.y, d.frame.width, d.frame.height),
119 }
120 })
121 .collect()
122 }
123}
124
125unsafe fn collect_applications(content: *const c_void) -> Vec<ApplicationSnapshot> {
126 unsafe {
127 let mut packed: Vec<MaybeUninit<FFIApplicationData>> = Vec::with_capacity(MAX_APPS);
128 let mut strings: Vec<i8> = vec![0; STRING_POOL_BYTES];
129 let mut strings_used: isize = 0;
130
131 let written = crate::ffi::sc_shareable_content_get_applications_batch(
132 content,
133 packed.as_mut_ptr().cast::<c_void>(),
134 MAX_APPS as isize,
135 strings.as_mut_ptr(),
136 STRING_POOL_BYTES as isize,
137 &mut strings_used,
138 );
139 if written <= 0 {
140 return Vec::new();
141 }
142 let count = (written as usize).min(MAX_APPS);
143
144 let pool: &[u8] = std::slice::from_raw_parts(
145 strings.as_ptr().cast::<u8>(),
146 (strings_used as usize).min(STRING_POOL_BYTES),
147 );
148
149 (0..count)
150 .map(|i| {
151 let app = packed[i].assume_init();
152 ApplicationSnapshot {
153 process_id: app.process_id,
154 bundle_identifier: read_string(
155 pool,
156 app.bundle_id_offset,
157 app.bundle_id_length,
158 ),
159 application_name: read_string(pool, app.app_name_offset, app.app_name_length),
160 }
161 })
162 .collect()
163 }
164}
165
166unsafe fn collect_windows(content: *const c_void, app_count_hint: usize) -> Vec<WindowSnapshot> {
167 unsafe {
168 let mut packed: Vec<MaybeUninit<FFIWindowData>> = Vec::with_capacity(MAX_WINDOWS);
169 let mut strings: Vec<i8> = vec![0; STRING_POOL_BYTES];
170 let mut strings_used: isize = 0;
171 let app_cap = MAX_APPS.max(app_count_hint);
176 let mut app_pointers: Vec<*const c_void> = vec![std::ptr::null(); app_cap];
177 let mut app_count: isize = 0;
178
179 let written = crate::ffi::sc_shareable_content_get_windows_batch(
180 content,
181 packed.as_mut_ptr().cast::<c_void>(),
182 MAX_WINDOWS as isize,
183 strings.as_mut_ptr(),
184 STRING_POOL_BYTES as isize,
185 &mut strings_used,
186 app_pointers.as_mut_ptr(),
187 app_cap as isize,
188 &mut app_count,
189 );
190
191 let returned_apps = (app_count as usize).min(app_cap);
194 for &ptr in &app_pointers[..returned_apps] {
195 if !ptr.is_null() {
196 crate::ffi::sc_running_application_release(ptr);
197 }
198 }
199
200 if written <= 0 {
201 return Vec::new();
202 }
203 let count = (written as usize).min(MAX_WINDOWS);
204
205 let pool: &[u8] = std::slice::from_raw_parts(
206 strings.as_ptr().cast::<u8>(),
207 (strings_used as usize).min(STRING_POOL_BYTES),
208 );
209
210 (0..count)
211 .map(|i| {
212 let w = packed[i].assume_init();
213 let title = if w.title_length == 0 {
214 None
215 } else {
216 let s = read_string(pool, w.title_offset, w.title_length);
217 if s.is_empty() {
218 None
219 } else {
220 Some(s)
221 }
222 };
223 WindowSnapshot {
224 window_id: w.window_id,
225 window_layer: w.window_layer,
226 is_on_screen: w.is_on_screen,
227 is_active: w.is_active,
228 frame: CGRect::new(w.frame.x, w.frame.y, w.frame.width, w.frame.height),
229 title,
230 owning_app_index: if w.owning_app_index < 0 {
231 None
232 } else {
233 Some(w.owning_app_index as usize)
234 },
235 }
236 })
237 .collect()
238 }
239}
240
241fn read_string(pool: &[u8], offset: u32, length: u32) -> String {
242 read_str(pool, offset, length).map_or_else(String::new, str::to_owned)
243}
244
245fn read_str(pool: &[u8], offset: u32, length: u32) -> Option<&str> {
251 let start = offset as usize;
252 let end = start.saturating_add(length as usize);
253 let bytes = pool.get(start..end)?;
254 std::str::from_utf8(bytes).ok()
255}