use std::path::Path;
use super::ScreenshotError;
pub(crate) fn capture_cgwindow(window_id: u32, path: &Path) -> Result<(), ScreenshotError> {
use core_graphics::geometry::{CGPoint, CGRect, CGSize};
use core_graphics::window::{
create_image, kCGWindowImageBoundsIgnoreFraming, kCGWindowImageNominalResolution,
kCGWindowListOptionIncludingWindow,
};
let rect = CGRect::new(&CGPoint::new(0.0, 0.0), &CGSize::new(f64::INFINITY, f64::INFINITY));
let cg_image = create_image(
rect,
kCGWindowListOptionIncludingWindow,
window_id,
kCGWindowImageBoundsIgnoreFraming | kCGWindowImageNominalResolution,
)
.ok_or_else(|| ScreenshotError::CaptureFailed { message: "CGWindowListCreateImage returned null".to_owned() })?;
let width = cg_image.width();
let height = cg_image.height();
let bytes_per_row = cg_image.bytes_per_row();
let data = cg_image.data();
let src = data.bytes();
let width_u32 = u32::try_from(width)
.map_err(|_| ScreenshotError::CaptureFailed { message: "width does not fit in u32".to_owned() })?;
let height_u32 = u32::try_from(height)
.map_err(|_| ScreenshotError::CaptureFailed { message: "height does not fit in u32".to_owned() })?;
let row_bytes = width
.checked_mul(4)
.ok_or_else(|| ScreenshotError::CaptureFailed { message: "row size overflow".to_owned() })?;
let total = row_bytes
.checked_mul(height)
.ok_or_else(|| ScreenshotError::CaptureFailed { message: "buffer size overflow".to_owned() })?;
let mut rgba: Vec<u8> = Vec::with_capacity(total);
for y in 0..height {
let row_start = y * bytes_per_row;
let row_end = row_start
.checked_add(row_bytes)
.ok_or_else(|| ScreenshotError::CaptureFailed { message: "CGImage row offset overflow".to_owned() })?;
if row_end > src.len() {
return Err(ScreenshotError::CaptureFailed { message: "CGImage row out of bounds".to_owned() });
}
let row = &src[row_start..row_end];
for px in row.chunks_exact(4) {
let blue = px[0];
let green = px[1];
let red = px[2];
let alpha = px[3];
let (red_out, green_out, blue_out) = if alpha == 0 {
(0u8, 0u8, 0u8)
} else if alpha == 255 {
(red, green, blue)
} else {
let alpha_u16 = u16::from(alpha);
let demul = |channel: u8| {
let value = (u16::from(channel) * 255 + alpha_u16 / 2) / alpha_u16;
value.min(255) as u8
};
(demul(red), demul(green), demul(blue))
};
rgba.extend_from_slice(&[red_out, green_out, blue_out, alpha]);
}
}
image::save_buffer(path, &rgba, width_u32, height_u32, image::ColorType::Rgba8).map_err(|err| {
ScreenshotError::CaptureFailed { message: format!("write cgwindow png {}: {err}", path.display()) }
})
}