diff --git a/src/core.rs b/src/core.rs index 0b717a8..131c312 100644 --- a/src/core.rs +++ b/src/core.rs @@ -5,28 +5,38 @@ use x11rb::rust_connection::RustConnection; use png::{BitDepth, ColorType, Encoder}; use std::fs::File; -/// Holds an open X11 connection and basic screen geometry. -/// All screenshot binaries share this context. +/// Stores: +/// - a connection to the X11 server (`conn`) +/// - the root window (the whole screen) +/// - the screen width and height in pixels +/// +/// Every screenshot function uses this. pub struct XContext { pub conn: RustConnection, - pub root: u32, // The root window of the screen (represents the whole desktop) - pub width: u16, // Screen width in pixels - pub height: u16, // Screen height in pixels + pub root: u32, + pub width: u16, + pub height: u16, } impl XContext { + /// Create a new connection to the X11 server and read basic screen info. + /// + /// This does not take a screenshot. It only connects and stores + /// values we need later. pub fn new() -> anyhow::Result { - // Establish a connection to the running X server. - // `screen_num` selects which screen we are connected to (multi-head setups). + // Connect to the X11 server running on the system. let (conn, screen_num) = RustConnection::connect(None)?; - // Extract the geometry information from the X11 setup. - // These fields are Copy, so borrowing ends immediately. - let screen = &conn.setup().roots[screen_num]; + // Read screen information from the X11 setup data. + // We copy only the simple numeric values we need. + let setup = conn.setup(); + let screen = &setup.roots[screen_num]; + let root = screen.root; let width = screen.width_in_pixels; let height = screen.height_in_pixels; + // Return our context object. Ok(Self { conn, root, @@ -36,66 +46,67 @@ impl XContext { } } -/// Capture the *entire* root window (full screen) into an RGBA8 buffer. +/// Capture a rectangular part of the screen. /// -/// X11 provides the `GetImage` request, which returns raw pixel data from a drawable. -/// We request the `ZPixmap` format, which returns packed pixel values. +/// `ctx` – connection + screen info +/// `x,y` – top-left corner of the rectangle +/// `w,h` – size of the rectangle /// -/// Many x11rb code generators include a named constant for `ZPixmap`. -/// If not available, its protocol value is simply `2`. -pub fn capture_root_image(ctx: &XContext) -> anyhow::Result> { - // ZPixmap corresponds to the X11 format that returns full pixel values. - // Using a literal here avoids dependency on generated constant names. +/// Returns: a `Vec` containing RGBA pixel data. +pub fn capture_rect(ctx: &XContext, x: u16, y: u16, w: u16, h: u16) -> anyhow::Result> { + // X11 uses the number 2 for "ZPixmap", which means: return full pixel data. let format_zpixmap: u8 = 2; - // Send the GetImage request for the entire screen. + // Ask X11 to send us the raw pixels in the requested rectangle. + // This returns X11's native pixel format (usually B,G,R,unused). let reply = ctx .conn .get_image( - format_zpixmap.into(), // ZPixmap format - ctx.root, // The root window (whole desktop) - 0, // X origin - 0, // Y origin - ctx.width, // Width to capture - ctx.height, // Height to capture - u32::MAX, // Plane mask: capture all pixel planes + format_zpixmap.into(), + ctx.root, + x as i16, + y as i16, + w, + h, + u32::MAX, )? .reply()?; let data = reply.data; - let depth = reply.depth; - // Modern X11 desktops typically use depth 24 (RGB) or 32 (RGB with padding). - if depth != 24 && depth != 32 { - anyhow::bail!("Unsupported X11 depth: {depth}"); - } - - // X11 typically stores pixels in little-endian B,G,R,(unused) format. - // We convert it to standard RGBA because PNG expects that. - let mut out = Vec::with_capacity((ctx.width as usize) * (ctx.height as usize) * 4); + // Prepare a buffer for RGBA output. + // Each pixel = 4 bytes (R, G, B, A). + let mut out = Vec::with_capacity((w as usize) * (h as usize) * 4); + // Convert each incoming pixel from X11's format (B, G, R, X) + // into the common RGBA format that PNG expects. for chunk in data.chunks_exact(4) { let b = chunk[0]; let g = chunk[1]; let r = chunk[2]; - out.extend_from_slice(&[r, g, b, 0xFF]); // Always opaque + + // A (alpha) is set to 255 = fully opaque. + out.extend_from_slice(&[r, g, b, 0xFF]); } Ok(out) } -/// Save a raw RGBA8 buffer as a PNG image at `path`. +/// Save an RGBA pixel buffer as a PNG file. /// -/// The png crate handles all compression and file formatting. -/// We simply specify: -/// - RGBA layout -/// - 8 bits per channel +/// `path` – where to write the file +/// `width` – image width in pixels +/// `height` – image height in pixels +/// `rgba` – pixel data in RGBA format pub fn save_png(path: &str, width: u16, height: u16, rgba: &[u8]) -> anyhow::Result<()> { let file = File::create(path)?; + + // Create a PNG encoder for an RGBA 8-bit image. let mut encoder = Encoder::new(file, width.into(), height.into()); encoder.set_color(ColorType::Rgba); encoder.set_depth(BitDepth::Eight); + // Write PNG header + pixel data. let mut writer = encoder.write_header()?; writer.write_image_data(rgba)?; writer.finish()?; diff --git a/src/fullscreen.rs b/src/fullscreen.rs index 4bcfd5a..aa20a12 100644 --- a/src/fullscreen.rs +++ b/src/fullscreen.rs @@ -1,38 +1,42 @@ mod core; use chrono::Local; -use core::{XContext, capture_root_image, save_png}; +use core::{XContext, capture_rect, save_png}; use std::fs; fn main() -> anyhow::Result<()> { - // Establish connection to X11 and gather screen info. + // Create a connection to the X11 server and read screen information. let ctx = XContext::new()?; - // Capture the entire screen as an RGBA8 buffer. - let img = capture_root_image(&ctx)?; + // Capture the entire screen. + // We pass (0,0) as the top-left corner and use the screen width/height. + let img = capture_rect(&ctx, 0, 0, ctx.width, ctx.height)?; - // Build an output path: - // ~/Pictures/Screenshots/screenshot_YYYYMMDDHHMMSS.png + // Build the output file path. + // Example: + // ~/Pictures/Screenshots/screenshot_20250116094530.png // - // Using timestamps avoids overwriting previous screenshots, - // and matches the behavior of many modern screenshot tools. + // Using a timestamp prevents overwriting old screenshots. let timestamp = Local::now().format("%Y%m%d%H%M%S").to_string(); + // Start from the user's home directory (fallback: current dir). let mut path = dirs::home_dir().unwrap_or_else(|| ".".into()); path.push("Pictures"); path.push("Screenshots"); - // Ensure the directory exists + // Make sure the directory exists. fs::create_dir_all(&path)?; + // Add the actual filename. path.push(format!("screenshot_{timestamp}.png")); - // Convert PathBuf → &str (safe because it’s UTF-8 on all Linux systems). + // Convert path to string for save_png(). let path_str = path.to_string_lossy(); - // Write the PNG file to disk. + // Write the captured RGBA image to a PNG file. save_png(&path_str, ctx.width, ctx.height, &img)?; println!("Saved {path_str}"); + Ok(()) }