diff --git a/src/main.rs b/src/main.rs index e7a11a9..5772596 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,164 @@ -fn main() { - println!("Hello, world!"); +use anyhow::Result; +use std::thread; +use std::time::Duration; +use x11rb::wrapper::ConnectionExt as _; +use x11rb::{ + connection::Connection, + protocol::xproto::{ + AtomEnum, ChangeWindowAttributesAux, CloseDown, ConnectionExt, CreateGCAux, EventMask, + PropMode, Rectangle, + }, + rust_connection::RustConnection, +}; + +fn main() -> Result<()> { + // Connect to the running graphical session, the X11 server. + let (conn, screen_num) = RustConnection::connect(None)?; + // Select the current screen. + let screen = &conn.setup().roots[screen_num]; + // Find the "root window," i.e., the desktop where you set the background. + let root = screen.root; + + // Short delay to avoid race conditions that can cause the WM to overwrite the wallpaper. + thread::sleep(Duration::from_millis(200)); + + // Ensure X11 resources created by this process remain valid after exit. + // To keep the wallpaper persistent after `icing` exits. + conn.set_close_down_mode(CloseDown::RETAIN_PERMANENT)?; + + // Atom (property) used to track ownership of the current wallpaper pixmap. + // This is read to find and clean up the previous wallpaper owner. + let atom_eset = conn.intern_atom(false, b"ESETROOT_PMAP_ID")?.reply()?.atom; + + // Remove any previously registered wallpaper pixmap. + // This prevents resource leaks and forces consumers to refresh. + if let Ok(reply) = conn + // Look for previous background image pixmap. + .get_property(false, root, atom_eset, AtomEnum::PIXMAP, 0, 1)? + .reply() + // Get any existing old one, if it exists. + && let Some(old) = reply.value32().and_then(|mut v| v.next()) + { + // Kill the client that owns the old wallpaper pixmap. + // Allows X server to free old pixmap and forces clients to refresh if cached. + conn.kill_client(old)?; + } + + // Get screen dimensions and color depth + let width = screen.width_in_pixels; + let height = screen.height_in_pixels; + let depth = screen.root_depth; + + // Create a pixmap (image) matching the screen size and depth. + // This pixmap will become the desktop background. + let pixmap = conn.generate_id()?; + conn.create_pixmap(depth, pixmap, root, width, height)?; + + // Fill the pixmap with the pixels to be set as the background. + // For now, allocate a solid color (RGB 26,132,74 scaled to X11 16-bit format). + let color = conn + .alloc_color(screen.default_colormap, 26 * 257, 132 * 257, 74 * 257)? + .reply()? + .pixel; + + // Create a Graphics Context (GC), which defines how drawing is done. + // It stores drawing parameters such as color, fill style, etc. + let gc = conn.generate_id()?; + // Associate the GC with the pixmap and configure its foreground color. + conn.create_gc(gc, pixmap, &CreateGCAux::new().foreground(color))?; + + // Paint a solid-color rectangle onto the pixmap using the GC. + // This is only for the solid-color case and will be replaced when using real images. + conn.poly_fill_rectangle( + pixmap, // BG Image (pixmap) + gc, // The Graphics Context to paint on + &[Rectangle { + x: 0, + y: 0, // Start from top-left corner + width, + height, // Set rectangle dimensions = screen width, height + }], + )?; + + // Grab and lock the X server to prevent other clients from observing or reacting to intermediate state. + // Prevents the WM or other apps from acting (repainting, caching, etc.) while the background is being updated. + conn.grab_server()?; + + // Temporarily suppress root window event notifications to the WM until the process is done. + // Prevent the WM from reacting mid-update, reading outdated data, repainting the background, or ignoring the final background. + conn.change_window_attributes( + root, + &ChangeWindowAttributesAux::new().event_mask(EventMask::NO_EVENT), + )?; + + // Core operation: assign the pixmap as the root window's background. Aka set the wallpaper. + conn.change_window_attributes( + root, + &ChangeWindowAttributesAux::new().background_pixmap(pixmap), + )?; + + // Request the root window to redraw itself using its current background pixmap. + // "Clear area": Historical X11 terminology stuck without name change. + // Original meaning: "Erase drawn content on top, restore the window's (historically plain) background." + // Current meaning, practically: "Fill the region using the window's current background (color or pixmap)." + conn.clear_area( + false, // Exposures: false. Do not generate expose events (redraw requests) for clients; avoids waking other clients during the update. + root, // Window: The desktop window (root). + 0, 0, // x,y: Start at the top-left corner. + 0, 0, // Width, height: 0 means "the entire window." + )?; + + // Atoms (properties) used by WMs, compositors, and client apps (e.g., for transparency effects) to locate and reuse the wallpaper pixmap. + // There's no standard. Different apps rely on different properties: `_XROOTPMAP_ID`, `ESETROOT_PMAP_ID`, & `_XSETROOT_ID`. Better use all 3. + let atom_root = conn.intern_atom(false, b"_XROOTPMAP_ID")?.reply()?.atom; + let atom_setroot = conn.intern_atom(false, b"_XSETROOT_ID")?.reply()?.atom; + + // Set root window properties that publish the wallpaper pixmap location to consumers. + conn.change_property32( + PropMode::REPLACE, + root, + atom_root, // _XROOTPMAP_ID: For discovery. + AtomEnum::PIXMAP, + &[pixmap], + )?; + conn.change_property32( + PropMode::REPLACE, + root, + atom_eset, // ESETROOT_PMAP_ID: For discovery and ownership / lifecycle. + AtomEnum::PIXMAP, + &[pixmap], + )?; + conn.change_property32( + PropMode::REPLACE, + root, + atom_setroot, // _XSETROOT_ID: For discovery, but legacy. + AtomEnum::PIXMAP, + &[pixmap], + )?; + + // Restore root window event notifications required by the window manager. + // Without these, the WM cannot track window creation, layout changes, or property updates, which breaks normal window management. + conn.change_window_attributes( + root, + &ChangeWindowAttributesAux::new().event_mask( + // Allows the WM to control and manage child windows (positioning, tiling, etc.). + EventMask::SUBSTRUCTURE_REDIRECT + // Notifies the WM about changes to managed windows (open, close, move, etc.). + | EventMask::SUBSTRUCTURE_NOTIFY + // Notifies listeners when root window properties change (like wallpaper). This is mainly what was disabled earlier. + | EventMask::PROPERTY_CHANGE, + ), + )?; + + // Release the server lock and allow normal processing to resume, since the background is set. + conn.ungrab_server()?; + + // Send all required updates in queue to X11, refresh immediately. + // Ensure the requests are sent and everything's done. + conn.flush()?; + + // Short delay to ensure the server processes all requests before `icing` exits. + thread::sleep(Duration::from_millis(50)); + + Ok(()) }