#![allow(
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_possible_truncation
)]
use crate::cg::CGRect;
use crate::ffi::{FFIApplicationData, FFIDisplayData, FFIWindowData};
use std::ffi::c_void;
const MAX_DISPLAYS: usize = 64;
const MAX_WINDOWS: usize = 4096;
const MAX_APPS: usize = 1024;
const STRING_POOL_BYTES: usize = 256 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisplaySnapshot {
pub display_id: u32,
pub width: i32,
pub height: i32,
pub frame: CGRect,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ApplicationSnapshot {
pub process_id: i32,
pub bundle_identifier: String,
pub application_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WindowSnapshot {
pub window_id: u32,
pub window_layer: i32,
pub is_on_screen: bool,
pub is_active: bool,
pub frame: CGRect,
pub title: Option<String>,
pub owning_app_index: Option<usize>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct ContentSnapshot {
pub displays: Vec<DisplaySnapshot>,
pub applications: Vec<ApplicationSnapshot>,
pub windows: Vec<WindowSnapshot>,
}
impl ContentSnapshot {
pub(crate) fn collect(content: *const c_void) -> Option<Self> {
if content.is_null() {
return None;
}
let displays = unsafe { collect_displays(content) };
let applications = unsafe { collect_applications(content) };
let windows = unsafe { collect_windows(content, applications.len()) };
Some(Self {
displays,
applications,
windows,
})
}
}
unsafe fn collect_displays(content: *const c_void) -> Vec<DisplaySnapshot> {
let mut buffer: Vec<FFIDisplayData> = Vec::with_capacity(MAX_DISPLAYS);
let written = crate::ffi::sc_shareable_content_get_displays_batch(
content,
buffer.as_mut_ptr().cast::<c_void>(),
MAX_DISPLAYS as isize,
);
if written <= 0 {
return Vec::new();
}
let count = (written as usize).min(MAX_DISPLAYS);
buffer.set_len(count);
buffer
.into_iter()
.map(|d| DisplaySnapshot {
display_id: d.display_id,
width: d.width,
height: d.height,
frame: CGRect::new(d.frame.x, d.frame.y, d.frame.width, d.frame.height),
})
.collect()
}
unsafe fn collect_applications(content: *const c_void) -> Vec<ApplicationSnapshot> {
let mut packed: Vec<FFIApplicationData> = Vec::with_capacity(MAX_APPS);
let mut strings: Vec<i8> = vec![0; STRING_POOL_BYTES];
let mut strings_used: isize = 0;
let written = crate::ffi::sc_shareable_content_get_applications_batch(
content,
packed.as_mut_ptr().cast::<c_void>(),
MAX_APPS as isize,
strings.as_mut_ptr(),
STRING_POOL_BYTES as isize,
&mut strings_used,
);
if written <= 0 {
return Vec::new();
}
let count = (written as usize).min(MAX_APPS);
packed.set_len(count);
let pool: &[u8] = std::slice::from_raw_parts(
strings.as_ptr().cast::<u8>(),
(strings_used as usize).min(STRING_POOL_BYTES),
);
packed
.into_iter()
.map(|app| ApplicationSnapshot {
process_id: app.process_id,
bundle_identifier: read_string(pool, app.bundle_id_offset, app.bundle_id_length),
application_name: read_string(pool, app.app_name_offset, app.app_name_length),
})
.collect()
}
unsafe fn collect_windows(content: *const c_void, app_count_hint: usize) -> Vec<WindowSnapshot> {
let mut packed: Vec<FFIWindowData> = Vec::with_capacity(MAX_WINDOWS);
let mut strings: Vec<i8> = vec![0; STRING_POOL_BYTES];
let mut strings_used: isize = 0;
let app_cap = MAX_APPS.max(app_count_hint);
let mut app_pointers: Vec<*const c_void> = vec![std::ptr::null(); app_cap];
let mut app_count: isize = 0;
let written = crate::ffi::sc_shareable_content_get_windows_batch(
content,
packed.as_mut_ptr().cast::<c_void>(),
MAX_WINDOWS as isize,
strings.as_mut_ptr(),
STRING_POOL_BYTES as isize,
&mut strings_used,
app_pointers.as_mut_ptr(),
app_cap as isize,
&mut app_count,
);
let returned_apps = (app_count as usize).min(app_cap);
for &ptr in &app_pointers[..returned_apps] {
if !ptr.is_null() {
crate::ffi::sc_running_application_release(ptr);
}
}
if written <= 0 {
return Vec::new();
}
let count = (written as usize).min(MAX_WINDOWS);
packed.set_len(count);
let pool: &[u8] = std::slice::from_raw_parts(
strings.as_ptr().cast::<u8>(),
(strings_used as usize).min(STRING_POOL_BYTES),
);
packed
.into_iter()
.map(|w| {
let title = if w.title_length == 0 {
None
} else {
let s = read_string(pool, w.title_offset, w.title_length);
if s.is_empty() {
None
} else {
Some(s)
}
};
WindowSnapshot {
window_id: w.window_id,
window_layer: w.window_layer,
is_on_screen: w.is_on_screen,
is_active: w.is_active,
frame: CGRect::new(w.frame.x, w.frame.y, w.frame.width, w.frame.height),
title,
owning_app_index: if w.owning_app_index < 0 {
None
} else {
Some(w.owning_app_index as usize)
},
}
})
.collect()
}
fn read_string(pool: &[u8], offset: u32, length: u32) -> String {
let start = offset as usize;
let end = start.saturating_add(length as usize);
if end > pool.len() {
return String::new();
}
String::from_utf8_lossy(&pool[start..end]).into_owned()
}