use super::image_utils::bgra_to_rgba;
use super::Screen;
use image::RgbaImage;
use std::ptr;
use x11::xlib::{
Display as XDisplay, XCloseDisplay, XDefaultRootWindow, XGetImage, XImage, XOpenDisplay,
XScreenCount, XScreenOfDisplay, ZPixmap,
};
struct DisplayGuard {
dpy: *mut XDisplay,
}
impl DisplayGuard {
fn open() -> Result<Self, String> {
let dpy = unsafe { XOpenDisplay(ptr::null()) };
if dpy.is_null() {
return Err("XOpenDisplay failed — is DISPLAY set and an X server running?".to_string());
}
Ok(Self { dpy })
}
}
impl Drop for DisplayGuard {
fn drop(&mut self) {
if !self.dpy.is_null() {
unsafe {
XCloseDisplay(self.dpy);
}
}
}
}
pub fn all() -> Result<Vec<Screen>, String> {
let display = DisplayGuard::open()?;
let n = unsafe { XScreenCount(display.dpy) };
let mut out = Vec::with_capacity(n as usize);
for i in 0..n {
let screen = unsafe { XScreenOfDisplay(display.dpy, i) };
if screen.is_null() {
continue;
}
let (w, h) = unsafe { ((*screen).width as u32, (*screen).height as u32) };
out.push(Screen {
id: i as u32,
x: 0,
y: 0,
width: w,
height: h,
scale_factor: 1.0,
});
}
if out.is_empty() {
Err("no X screens found".to_string())
} else {
Ok(out)
}
}
fn grab(x: i32, y: i32, width: u32, height: u32) -> Result<RgbaImage, String> {
if width == 0 || height == 0 {
return Err("capture area is empty".to_string());
}
let display = DisplayGuard::open()?;
let root = unsafe { XDefaultRootWindow(display.dpy) };
let img_ptr = unsafe { XGetImage(display.dpy, root, x, y, width, height, u64::MAX, ZPixmap) };
if img_ptr.is_null() {
return Err("XGetImage returned NULL — coordinates may be outside the root window".to_string());
}
let result = unsafe { read_ximage(img_ptr, width, height) };
unsafe {
let destroy = (*img_ptr).funcs.destroy_image;
if let Some(f) = destroy {
f(img_ptr);
}
}
result
}
unsafe fn read_ximage(img_ptr: *mut XImage, width: u32, height: u32) -> Result<RgbaImage, String> {
let img = &*img_ptr;
let depth = img.depth as u32;
let bytes_per_pixel = (img.bits_per_pixel / 8) as usize;
let stride = img.bytes_per_line as usize;
let data = std::slice::from_raw_parts(img.data as *const u8, stride * height as usize);
let mut bgra = vec![0u8; (width as usize) * (height as usize) * 4];
for row in 0..height as usize {
let src_row = &data[row * stride..];
for col in 0..width as usize {
let src = &src_row[col * bytes_per_pixel..];
let dst = col * 4;
match bytes_per_pixel {
4 => {
bgra[dst..dst + 4].copy_from_slice(&src[..4]);
}
3 => {
bgra[dst] = src[0]; bgra[dst + 1] = src[1]; bgra[dst + 2] = src[2]; bgra[dst + 3] = 255; }
_ => {
return Err(format!("unsupported bits_per_pixel for depth {depth}"));
}
}
}
}
bgra_to_rgba(width, height, bgra)
}
pub fn capture(screen: &Screen) -> Result<RgbaImage, String> {
let w = ((screen.width as f32) * screen.scale_factor) as u32;
let h = ((screen.height as f32) * screen.scale_factor) as u32;
grab(screen.x, screen.y, w, h)
}
pub fn capture_area(screen: &Screen, x: i32, y: i32, width: u32, height: u32) -> Result<RgbaImage, String> {
let ax = screen.x + ((x as f32) * screen.scale_factor) as i32;
let ay = screen.y + ((y as f32) * screen.scale_factor) as i32;
let w = ((width as f32) * screen.scale_factor) as u32;
let h = ((height as f32) * screen.scale_factor) as u32;
grab(ax, ay, w, h)
}