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;
19
20const MAX_DISPLAYS: usize = 64;
25const MAX_WINDOWS: usize = 4096;
26const MAX_APPS: usize = 1024;
27const STRING_POOL_BYTES: usize = 256 * 1024;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct DisplaySnapshot {
32 pub display_id: u32,
33 pub width: i32,
34 pub height: i32,
35 pub frame: CGRect,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct ApplicationSnapshot {
41 pub process_id: i32,
42 pub bundle_identifier: String,
43 pub application_name: String,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct WindowSnapshot {
49 pub window_id: u32,
50 pub window_layer: i32,
51 pub is_on_screen: bool,
52 pub is_active: bool,
53 pub frame: CGRect,
54 pub title: Option<String>,
55 pub owning_app_index: Option<usize>,
59}
60
61#[derive(Debug, Default, Clone, PartialEq, Eq)]
63pub struct ContentSnapshot {
64 pub displays: Vec<DisplaySnapshot>,
65 pub applications: Vec<ApplicationSnapshot>,
66 pub windows: Vec<WindowSnapshot>,
67}
68
69impl ContentSnapshot {
70 pub(crate) fn collect(content: *const c_void) -> Option<Self> {
73 if content.is_null() {
74 return None;
75 }
76
77 let displays = unsafe { collect_displays(content) };
82 let applications = unsafe { collect_applications(content) };
83 let windows = unsafe { collect_windows(content, applications.len()) };
84
85 Some(Self {
86 displays,
87 applications,
88 windows,
89 })
90 }
91}
92
93unsafe fn collect_displays(content: *const c_void) -> Vec<DisplaySnapshot> {
94 let mut buffer: Vec<FFIDisplayData> = Vec::with_capacity(MAX_DISPLAYS);
95 let written = crate::ffi::sc_shareable_content_get_displays_batch(
98 content,
99 buffer.as_mut_ptr().cast::<c_void>(),
100 MAX_DISPLAYS as isize,
101 );
102 if written <= 0 {
103 return Vec::new();
104 }
105 let count = (written as usize).min(MAX_DISPLAYS);
106 buffer.set_len(count);
107
108 buffer
109 .into_iter()
110 .map(|d| DisplaySnapshot {
111 display_id: d.display_id,
112 width: d.width,
113 height: d.height,
114 frame: CGRect::new(d.frame.x, d.frame.y, d.frame.width, d.frame.height),
115 })
116 .collect()
117}
118
119unsafe fn collect_applications(content: *const c_void) -> Vec<ApplicationSnapshot> {
120 let mut packed: Vec<FFIApplicationData> = Vec::with_capacity(MAX_APPS);
121 let mut strings: Vec<i8> = vec![0; STRING_POOL_BYTES];
122 let mut strings_used: isize = 0;
123
124 let written = crate::ffi::sc_shareable_content_get_applications_batch(
125 content,
126 packed.as_mut_ptr().cast::<c_void>(),
127 MAX_APPS as isize,
128 strings.as_mut_ptr(),
129 STRING_POOL_BYTES as isize,
130 &mut strings_used,
131 );
132 if written <= 0 {
133 return Vec::new();
134 }
135 let count = (written as usize).min(MAX_APPS);
136 packed.set_len(count);
137
138 let pool: &[u8] = std::slice::from_raw_parts(
139 strings.as_ptr().cast::<u8>(),
140 (strings_used as usize).min(STRING_POOL_BYTES),
141 );
142
143 packed
144 .into_iter()
145 .map(|app| ApplicationSnapshot {
146 process_id: app.process_id,
147 bundle_identifier: read_string(pool, app.bundle_id_offset, app.bundle_id_length),
148 application_name: read_string(pool, app.app_name_offset, app.app_name_length),
149 })
150 .collect()
151}
152
153unsafe fn collect_windows(content: *const c_void, app_count_hint: usize) -> Vec<WindowSnapshot> {
154 let mut packed: Vec<FFIWindowData> = Vec::with_capacity(MAX_WINDOWS);
155 let mut strings: Vec<i8> = vec![0; STRING_POOL_BYTES];
156 let mut strings_used: isize = 0;
157 let app_cap = MAX_APPS.max(app_count_hint);
162 let mut app_pointers: Vec<*const c_void> = vec![std::ptr::null(); app_cap];
163 let mut app_count: isize = 0;
164
165 let written = crate::ffi::sc_shareable_content_get_windows_batch(
166 content,
167 packed.as_mut_ptr().cast::<c_void>(),
168 MAX_WINDOWS as isize,
169 strings.as_mut_ptr(),
170 STRING_POOL_BYTES as isize,
171 &mut strings_used,
172 app_pointers.as_mut_ptr(),
173 app_cap as isize,
174 &mut app_count,
175 );
176
177 let returned_apps = (app_count as usize).min(app_cap);
180 for &ptr in &app_pointers[..returned_apps] {
181 if !ptr.is_null() {
182 crate::ffi::sc_running_application_release(ptr);
183 }
184 }
185
186 if written <= 0 {
187 return Vec::new();
188 }
189 let count = (written as usize).min(MAX_WINDOWS);
190 packed.set_len(count);
191
192 let pool: &[u8] = std::slice::from_raw_parts(
193 strings.as_ptr().cast::<u8>(),
194 (strings_used as usize).min(STRING_POOL_BYTES),
195 );
196
197 packed
198 .into_iter()
199 .map(|w| {
200 let title = if w.title_length == 0 {
201 None
202 } else {
203 let s = read_string(pool, w.title_offset, w.title_length);
204 if s.is_empty() {
205 None
206 } else {
207 Some(s)
208 }
209 };
210 WindowSnapshot {
211 window_id: w.window_id,
212 window_layer: w.window_layer,
213 is_on_screen: w.is_on_screen,
214 is_active: w.is_active,
215 frame: CGRect::new(w.frame.x, w.frame.y, w.frame.width, w.frame.height),
216 title,
217 owning_app_index: if w.owning_app_index < 0 {
218 None
219 } else {
220 Some(w.owning_app_index as usize)
221 },
222 }
223 })
224 .collect()
225}
226
227fn read_string(pool: &[u8], offset: u32, length: u32) -> String {
228 let start = offset as usize;
229 let end = start.saturating_add(length as usize);
230 if end > pool.len() {
231 return String::new();
232 }
233 String::from_utf8_lossy(&pool[start..end]).into_owned()
234}