use crate::{
capture::traits::{DefaultPixelConverter, PixelFormatConverter},
encoder::simd::global_simd_converter,
error::{CaptureError, CaptureResult},
memory_pool::global_pool,
types::{CaptureRegion, DisplayInfo, PixelFormat, RawImage},
};
use x11rb::{
connection::Connection,
protocol::{
randr::{self, ConnectionExt as RandrConnectionExt},
xfixes::{self, ConnectionExt as XfixesConnectionExt},
xproto::{self, ConnectionExt as XprotoConnectionExt, ImageFormat},
},
rust_connection::RustConnection,
};
pub struct X11Capture {
connection: RustConnection,
screen_num: usize,
root_window: xproto::Window,
pixel_converter: DefaultPixelConverter,
}
impl X11Capture {
pub fn new() -> CaptureResult<Self> {
let (connection, screen_num) = RustConnection::connect(None).map_err(|e| {
CaptureError::PlatformError(format!("Failed to connect to X11: {}", e))
})?;
let setup = connection.setup();
let screen = &setup.roots[screen_num];
let root_window = screen.root;
Ok(Self {
connection,
screen_num,
root_window,
pixel_converter: DefaultPixelConverter,
})
}
pub fn get_displays(&self) -> CaptureResult<Vec<DisplayInfo>> {
let mut displays = Vec::new();
let resources = randr::get_screen_resources(&self.connection, self.root_window)
.map_err(|e| CaptureError::DisplayEnumerationFailed(format!("XRandR error: {}", e)))?
.reply()
.map_err(|e| CaptureError::DisplayEnumerationFailed(format!("XRandR reply error: {}", e)))?;
for (index, &crtc) in resources.crtcs.iter().enumerate() {
let crtc_info = randr::get_crtc_info(
&self.connection,
crtc,
resources.config_timestamp,
)
.map_err(|e| CaptureError::DisplayEnumerationFailed(format!("CRTC info error: {}", e)))?
.reply()
.map_err(|e| CaptureError::DisplayEnumerationFailed(format!("CRTC reply error: {}", e)))?;
if crtc_info.mode == 0 || crtc_info.num_outputs == 0 {
continue;
}
let mode = resources
.modes
.iter()
.find(|m| m.id == crtc_info.mode)
.ok_or_else(|| {
CaptureError::DisplayEnumerationFailed("Mode not found".to_string())
})?;
let refresh_rate = if mode.htotal != 0 && mode.vtotal != 0 {
(mode.dot_clock as u64 * 1000 / (mode.htotal as u64 * mode.vtotal as u64)) as u32
} else {
60 };
displays.push(DisplayInfo {
index,
name: format!("Display {}", index + 1),
width: crtc_info.width as u32,
height: crtc_info.height as u32,
x: crtc_info.x as i32,
y: crtc_info.y as i32,
scale_factor: 1.0, is_primary: index == 0, refresh_rate,
color_depth: 24, });
}
if displays.is_empty() {
let setup = self.connection.setup();
let screen = &setup.roots[self.screen_num];
displays.push(DisplayInfo {
index: 0,
name: "Primary Display".to_string(),
width: screen.width_in_pixels as u32,
height: screen.height_in_pixels as u32,
x: 0,
y: 0,
scale_factor: 1.0,
is_primary: true,
refresh_rate: 60,
color_depth: screen.root_depth,
});
}
Ok(displays)
}
pub fn capture_display(&self, display_index: usize) -> CaptureResult<RawImage> {
let displays = self.get_displays()?;
let display = displays
.get(display_index)
.ok_or_else(|| CaptureError::DisplayNotFound(display_index))?;
self.capture_region(CaptureRegion {
x: display.x,
y: display.y,
width: display.width,
height: display.height,
})
}
pub fn capture_region(&self, region: CaptureRegion) -> CaptureResult<RawImage> {
let image_reply = xproto::get_image(
&self.connection,
ImageFormat::Z_PIXMAP,
self.root_window,
region.x as i16,
region.y as i16,
region.width as u16,
region.height as u16,
!0, )
.map_err(|e| CaptureError::CaptureFailed(format!("X11 GetImage error: {}", e)))?
.reply()
.map_err(|e| CaptureError::CaptureFailed(format!("X11 GetImage reply error: {}", e)))?;
let setup = self.connection.setup();
let screen = &setup.roots[self.screen_num];
let visual = setup
.roots
.iter()
.flat_map(|screen| &screen.allowed_depths)
.flat_map(|depth| &depth.visuals)
.find(|v| v.visual_id == screen.root_visual)
.ok_or_else(|| CaptureError::CaptureFailed("Visual not found".to_string()))?;
let (pixel_format, bytes_per_pixel) = match image_reply.depth {
24 | 32 => {
let is_bgr = self.is_bgr_format(visual);
if image_reply.depth == 32 {
(if is_bgr { PixelFormat::BGRA8 } else { PixelFormat::RGBA8 }, 4)
} else {
(if is_bgr { PixelFormat::BGR8 } else { PixelFormat::RGB8 }, 3)
}
}
depth => {
return Err(CaptureError::CaptureFailed(
format!("Unsupported bit depth: {}", depth),
))
}
};
let pool = global_pool();
let buffer_size = (region.width * region.height * bytes_per_pixel as u32) as usize;
let mut pooled_buffer = pool
.acquire(buffer_size)
.map_err(|_| CaptureError::MemoryAllocationFailed { size: buffer_size })?;
match pixel_format {
PixelFormat::BGRA8 => {
pooled_buffer.data_mut()[..image_reply.data.len()].copy_from_slice(&image_reply.data);
global_simd_converter().convert_bgra_to_rgba(pooled_buffer.data_mut());
}
PixelFormat::BGR8 => {
pooled_buffer.data_mut()[..image_reply.data.len()].copy_from_slice(&image_reply.data);
global_simd_converter().convert_bgr_to_rgb(pooled_buffer.data_mut());
}
PixelFormat::RGBA8 | PixelFormat::RGB8 => {
pooled_buffer.data_mut()[..image_reply.data.len()].copy_from_slice(&image_reply.data);
}
_ => unreachable!(),
}
let final_format = if bytes_per_pixel == 4 {
PixelFormat::RGBA8
} else {
PixelFormat::RGB8
};
let data = pooled_buffer.into_vec();
Ok(RawImage::new(data, region.width, region.height, final_format))
}
fn is_bgr_format(&self, visual: &xproto::Visualtype) -> bool {
visual.blue_mask < visual.red_mask
}
fn convert_bgra_to_rgba(&self, src: &[u8], dst: &mut [u8], width: u32, height: u32) {
let pixel_count = (width * height) as usize;
for i in 0..pixel_count {
let offset = i * 4;
dst[offset] = src[offset + 2]; dst[offset + 1] = src[offset + 1]; dst[offset + 2] = src[offset]; dst[offset + 3] = src[offset + 3]; }
}
fn convert_bgr_to_rgb(&self, src: &[u8], dst: &mut [u8], width: u32, height: u32) {
let pixel_count = (width * height) as usize;
for i in 0..pixel_count {
let offset = i * 3;
dst[offset] = src[offset + 2]; dst[offset + 1] = src[offset + 1]; dst[offset + 2] = src[offset]; }
}
pub fn capture_with_cursor(&self, region: CaptureRegion) -> CaptureResult<RawImage> {
let mut image = self.capture_region(region)?;
match xfixes::get_cursor_image(&self.connection) {
Ok(cursor_request) => {
match cursor_request.reply() {
Ok(cursor) => {
let cursor_x = cursor.x as i32 - region.x;
let cursor_y = cursor.y as i32 - region.y;
if cursor_x >= 0
&& cursor_y >= 0
&& cursor_x < region.width as i32
&& cursor_y < region.height as i32
{
self.blend_cursor(
&mut image.data,
&cursor.cursor_image,
cursor.width as u32,
cursor.height as u32,
cursor_x as u32,
cursor_y as u32,
region.width,
);
}
}
Err(e) => {
log::debug!("Failed to get cursor image: {}", e);
}
}
}
Err(e) => {
log::debug!("XFixes not available: {}", e);
}
}
Ok(image)
}
fn blend_cursor(
&self,
image: &mut [u8],
cursor_data: &[u32],
cursor_width: u32,
cursor_height: u32,
cursor_x: u32,
cursor_y: u32,
image_width: u32,
) {
for cy in 0..cursor_height {
for cx in 0..cursor_width {
let cursor_idx = (cy * cursor_width + cx) as usize;
let cursor_pixel = cursor_data[cursor_idx];
let alpha = ((cursor_pixel >> 24) & 0xFF) as u8;
if alpha == 0 {
continue; }
let red = ((cursor_pixel >> 16) & 0xFF) as u8;
let green = ((cursor_pixel >> 8) & 0xFF) as u8;
let blue = (cursor_pixel & 0xFF) as u8;
let img_x = cursor_x + cx;
let img_y = cursor_y + cy;
let img_idx = ((img_y * image_width + img_x) * 4) as usize;
if img_idx + 3 < image.len() {
let alpha_f = alpha as f32 / 255.0;
let inv_alpha = 1.0 - alpha_f;
image[img_idx] = (red as f32 * alpha_f + image[img_idx] as f32 * inv_alpha) as u8;
image[img_idx + 1] = (green as f32 * alpha_f + image[img_idx + 1] as f32 * inv_alpha) as u8;
image[img_idx + 2] = (blue as f32 * alpha_f + image[img_idx + 2] as f32 * inv_alpha) as u8;
}
}
}
}
}