@@ -0,0 +1,96 @@ |
| 1 | +//! Screenshot capture for garlock |
| 2 | +//! |
| 3 | +//! Captures the root window contents before displaying the locker. |
| 4 | + |
| 5 | +use anyhow::{Context, Result}; |
| 6 | +use x11rb::connection::Connection; |
| 7 | +use x11rb::protocol::xproto::{ConnectionExt, ImageFormat}; |
| 8 | + |
| 9 | +/// Captured screenshot data |
| 10 | +pub struct Screenshot { |
| 11 | + /// Raw pixel data in BGRA format (X11 native) |
| 12 | + pub data: Vec<u8>, |
| 13 | + /// Image width |
| 14 | + pub width: u32, |
| 15 | + /// Image height |
| 16 | + pub height: u32, |
| 17 | + /// Bits per pixel (typically 24 or 32) |
| 18 | + pub depth: u8, |
| 19 | +} |
| 20 | + |
| 21 | +impl Screenshot { |
| 22 | + /// Capture the root window contents |
| 23 | + /// |
| 24 | + /// This should be called BEFORE creating the locker window to capture |
| 25 | + /// the current desktop state. |
| 26 | + pub fn capture() -> Result<Self> { |
| 27 | + let (conn, screen_num) = |
| 28 | + x11rb::connect(None).context("Failed to connect to X server for screenshot")?; |
| 29 | + |
| 30 | + let screen = &conn.setup().roots[screen_num]; |
| 31 | + let root = screen.root; |
| 32 | + let width = screen.width_in_pixels; |
| 33 | + let height = screen.height_in_pixels; |
| 34 | + let depth = screen.root_depth; |
| 35 | + |
| 36 | + tracing::debug!(width, height, depth, "Capturing root window"); |
| 37 | + |
| 38 | + // Get image from root window |
| 39 | + let reply = conn |
| 40 | + .get_image( |
| 41 | + ImageFormat::Z_PIXMAP, |
| 42 | + root, |
| 43 | + 0, |
| 44 | + 0, |
| 45 | + width, |
| 46 | + height, |
| 47 | + !0, // plane_mask - all planes |
| 48 | + )? |
| 49 | + .reply() |
| 50 | + .context("Failed to get root window image")?; |
| 51 | + |
| 52 | + tracing::debug!( |
| 53 | + data_len = reply.data.len(), |
| 54 | + depth = reply.depth, |
| 55 | + "Screenshot captured" |
| 56 | + ); |
| 57 | + |
| 58 | + // Verify we got the expected amount of data |
| 59 | + let expected_size = width as usize * height as usize * 4; // 4 bytes per pixel for 32-bit |
| 60 | + if reply.data.len() < expected_size { |
| 61 | + tracing::warn!( |
| 62 | + "Screenshot data smaller than expected: {} < {}", |
| 63 | + reply.data.len(), |
| 64 | + expected_size |
| 65 | + ); |
| 66 | + } |
| 67 | + |
| 68 | + Ok(Self { |
| 69 | + data: reply.data, |
| 70 | + width: width as u32, |
| 71 | + height: height as u32, |
| 72 | + depth: reply.depth, |
| 73 | + }) |
| 74 | + } |
| 75 | + |
| 76 | + /// Convert BGRA data to RGBA for image processing |
| 77 | + pub fn to_rgba(&self) -> Vec<u8> { |
| 78 | + let mut rgba = self.data.clone(); |
| 79 | + bgra_to_rgba(&mut rgba); |
| 80 | + rgba |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +/// Convert BGRA pixel data to RGBA in-place |
| 85 | +pub fn bgra_to_rgba(data: &mut [u8]) { |
| 86 | + for chunk in data.chunks_exact_mut(4) { |
| 87 | + chunk.swap(0, 2); // B <-> R |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +/// Convert RGBA pixel data to BGRA in-place |
| 92 | +pub fn rgba_to_bgra(data: &mut [u8]) { |
| 93 | + for chunk in data.chunks_exact_mut(4) { |
| 94 | + chunk.swap(0, 2); // R <-> B |
| 95 | + } |
| 96 | +} |