use super::ScreenshotError;
#[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)]
pub(crate) struct DiscoveredWindow {
pub(crate) window_id: u32,
pub(crate) owner: String,
pub(crate) title: String,
pub(crate) layer: i32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct WindowBounds {
pub(crate) width: f64,
pub(crate) height: f64,
}
pub(crate) struct EnumeratedLayerZeroWindows {
pub(crate) available: Vec<DiscoveredWindow>,
pub(crate) target_bounds: Option<WindowBounds>,
}
pub(crate) const AVAILABLE_WINDOWS_CAP: usize = 20;
pub(crate) fn enumerate_layer_zero_windows(target: u32) -> Result<EnumeratedLayerZeroWindows, ScreenshotError> {
use core_foundation::array::CFArray;
use core_foundation::base::{CFType, TCFType};
use core_foundation::dictionary::CFDictionary;
use core_foundation::number::CFNumber;
use core_foundation::string::CFString;
use core_graphics::geometry::CGRect;
use core_graphics::window::{
CGWindowListCopyWindowInfo, kCGNullWindowID, kCGWindowBounds, kCGWindowLayer,
kCGWindowListExcludeDesktopElements, kCGWindowListOptionOnScreenOnly, kCGWindowName, kCGWindowNumber,
kCGWindowOwnerName,
};
let raw = unsafe {
CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
kCGNullWindowID,
)
};
if raw.is_null() {
return Err(ScreenshotError::CaptureFailed { message: "CGWindowListCopyWindowInfo returned null".to_owned() });
}
let windows: CFArray<CFDictionary<CFString, CFType>> = unsafe { TCFType::wrap_under_create_rule(raw) };
let owner_key = unsafe { CFString::wrap_under_get_rule(kCGWindowOwnerName) };
let title_key = unsafe { CFString::wrap_under_get_rule(kCGWindowName) };
let layer_key = unsafe { CFString::wrap_under_get_rule(kCGWindowLayer) };
let number_key = unsafe { CFString::wrap_under_get_rule(kCGWindowNumber) };
let bounds_key = unsafe { CFString::wrap_under_get_rule(kCGWindowBounds) };
let mut available: Vec<DiscoveredWindow> = Vec::new();
let mut target_bounds: Option<WindowBounds> = None;
for dict in windows.iter() {
let layer = dict.find(&layer_key).and_then(|v| v.downcast::<CFNumber>()).and_then(|v| v.to_i32()).unwrap_or(-1);
if layer != 0 {
continue;
}
let number_i64 =
dict.find(&number_key).and_then(|v| v.downcast::<CFNumber>()).and_then(|v| v.to_i64()).unwrap_or(0);
let Ok(window_id) = u32::try_from(number_i64) else {
continue;
};
if window_id == 0 {
continue;
}
let owner =
dict.find(&owner_key).and_then(|v| v.downcast::<CFString>()).map(|v| v.to_string()).unwrap_or_default();
let title =
dict.find(&title_key).and_then(|v| v.downcast::<CFString>()).map(|v| v.to_string()).unwrap_or_default();
if window_id == target && target_bounds.is_none() {
if let Some(bounds_dict) = dict.find(&bounds_key).and_then(|v| v.downcast::<CFDictionary>())
&& let Some(rect) = CGRect::from_dict_representation(&bounds_dict)
{
target_bounds = Some(WindowBounds { width: rect.size.width, height: rect.size.height });
}
}
if available.len() < AVAILABLE_WINDOWS_CAP {
available.push(DiscoveredWindow { window_id, owner, title, layer });
} else if target_bounds.is_some() {
break;
}
}
Ok(EnumeratedLayerZeroWindows { available, target_bounds })
}