x11-overlay 0.1.0

A library for creating overlay interfaces on X11 systems using Cairo for rendering
Documentation
use super::renderer::Renderer;
use super::text::TextRenderer;
use super::{Color, Rect};
use anyhow::{Context as AnyhowContext, Result};
use cairo::{XCBConnection as CairoXCBConnection, XCBDrawable, XCBSurface, XCBVisualType};
use x11rb::protocol::xproto::*;
use x11rb::xcb_ffi::XCBConnection;

pub struct GraphicsContext {
    x11_renderer: Renderer<'static>,
    text_renderer: TextRenderer,
    cairo_surface: Option<XCBSurface>,
    width: i32,
    height: i32,
}

impl GraphicsContext {
    pub fn new(
        conn: &'static XCBConnection,
        window: Window,
        visual: &Visualtype,
        width: i32,
        height: i32,
    ) -> Result<Self> {
        let x11_renderer = Renderer::new(conn, window);
        let text_renderer = TextRenderer::new(width, height)?;

        let mut context = Self {
            x11_renderer,
            text_renderer,
            cairo_surface: None,
            width,
            height,
        };

        context.setup_cairo_surface(conn, window, visual)?;
        Ok(context)
    }

    fn setup_cairo_surface(
        &mut self,
        conn: &XCBConnection,
        window: Window,
        visual: &Visualtype,
    ) -> Result<()> {
        println!("Setting up Cairo XCB surface:");
        println!("  Visual ID: 0x{:x}", visual.visual_id);
        println!("  Visual class: {:?}", visual.class);
        println!("  Bits per RGB: {}", visual.bits_per_rgb_value);
        println!("  Colormap entries: {}", visual.colormap_entries);
        println!("  Red mask: 0x{:x}", visual.red_mask);
        println!("  Green mask: 0x{:x}", visual.green_mask);
        println!("  Blue mask: 0x{:x}", visual.blue_mask);
        println!("  Window dimensions: {}x{}", self.width, self.height);

        let cairo_conn =
            unsafe { CairoXCBConnection::from_raw_none(conn.get_raw_xcb_connection() as *mut _) };

        let cairo_drawable = XCBDrawable(window);

        // Try different approaches to create the visual
        let mut attempts = Vec::new();

        // Attempt 1: Proper visual structure reconstruction
        // Create a proper xcb_visualtype_t structure from the x11rb Visualtype
        #[repr(C)]
        struct XcbVisualType {
            visual_id: u32,
            class: u8,
            bits_per_rgb_value: u8,
            colormap_entries: u16,
            red_mask: u32,
            green_mask: u32,
            blue_mask: u32,
            pad0: [u8; 4],
        }

        let xcb_visual = XcbVisualType {
            visual_id: visual.visual_id,
            class: match visual.class {
                x11rb::protocol::xproto::VisualClass::STATIC_GRAY => 0,
                x11rb::protocol::xproto::VisualClass::GRAY_SCALE => 1,
                x11rb::protocol::xproto::VisualClass::STATIC_COLOR => 2,
                x11rb::protocol::xproto::VisualClass::PSEUDO_COLOR => 3,
                x11rb::protocol::xproto::VisualClass::TRUE_COLOR => 4,
                x11rb::protocol::xproto::VisualClass::DIRECT_COLOR => 5,
                _ => 4, // Default to TRUE_COLOR for unknown types
            },
            bits_per_rgb_value: visual.bits_per_rgb_value,
            colormap_entries: visual.colormap_entries,
            red_mask: visual.red_mask,
            green_mask: visual.green_mask,
            blue_mask: visual.blue_mask,
            pad0: [0; 4],
        };

        let cairo_visual_1 =
            unsafe { XCBVisualType::from_raw_none(&xcb_visual as *const XcbVisualType as *mut _) };

        match XCBSurface::create(
            &cairo_conn,
            &cairo_drawable,
            &cairo_visual_1,
            self.width,
            self.height,
        ) {
            Ok(surface) => {
                self.cairo_surface = Some(surface);
                println!("✓ Cairo XCB surface created successfully (reconstructed visual)");
                return Ok(());
            }
            Err(e) => {
                attempts.push(format!("Reconstructed visual structure: {}", e));
            }
        }

        // Attempt 2: Try creating image surface instead of XCB surface
        match cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height) {
            Ok(_image_surface) => {
                // This isn't directly usable for X11, but let's see if it creates successfully
                println!("✓ Cairo image surface created successfully as fallback");
                // We can't use this directly with XCB, so continue to other attempts
                attempts.push("Image surface: Success (but not usable for X11)".to_string());
            }
            Err(e) => {
                attempts.push(format!("Image surface fallback: {}", e));
            }
        }

        // Attempt 3: Try with zero width/height to test basic functionality
        let cairo_visual_3 =
            unsafe { XCBVisualType::from_raw_none(&visual.visual_id as *const u32 as *mut _) };

        match XCBSurface::create(
            &cairo_conn,
            &cairo_drawable,
            &cairo_visual_3,
            1, // Try with minimal dimensions
            1,
        ) {
            Ok(_surface) => {
                // If minimal surface works, try full size
                match XCBSurface::create(
                    &cairo_conn,
                    &cairo_drawable,
                    &cairo_visual_3,
                    self.width,
                    self.height,
                ) {
                    Ok(full_surface) => {
                        self.cairo_surface = Some(full_surface);
                        println!("✓ Cairo XCB surface created successfully (full size after minimal test)");
                        return Ok(());
                    }
                    Err(e) => {
                        attempts.push(format!("Full size after minimal: {}", e));
                    }
                }
            }
            Err(e) => {
                attempts.push(format!("Minimal size test: {}", e));
            }
        }

        // All attempts failed
        eprintln!("✗ All Cairo XCB surface creation attempts failed:");
        for (i, attempt) in attempts.iter().enumerate() {
            eprintln!("  Attempt {}: {}", i + 1, attempt);
        }
        self.cairo_surface = None;
        Ok(())
    }

    pub fn resize(&mut self, width: i32, height: i32) -> Result<()> {
        self.width = width;
        self.height = height;
        self.text_renderer.resize(width, height)?;

        if let Some(ref surface) = self.cairo_surface {
            surface
                .set_size(width, height)
                .with_context(|| "Failed to resize Cairo surface")?;
        }

        Ok(())
    }

    pub fn clear(&mut self) -> Result<()> {
        self.x11_renderer.clear_area(super::renderer::Rectangle {
            x: 0,
            y: 0,
            width: self.width as u16,
            height: self.height as u16,
        })?;
        self.text_renderer.clear();
        Ok(())
    }

    pub fn clear_with_color(&mut self, color: Color) -> Result<()> {
        let x11_color = super::renderer::Color {
            argb: ((color.a * 255.0) as u32) << 24
                | ((color.r * 255.0) as u32) << 16
                | ((color.g * 255.0) as u32) << 8
                | ((color.b * 255.0) as u32),
        };

        self.x11_renderer.fill_rectangle(
            super::renderer::Rectangle {
                x: 0,
                y: 0,
                width: self.width as u16,
                height: self.height as u16,
            },
            x11_color,
        )?;

        self.text_renderer.clear_with_color(color);
        Ok(())
    }

    pub fn fill_rectangle(&mut self, rect: Rect, color: Color) -> Result<()> {
        let x11_color = super::renderer::Color {
            argb: ((color.a * 255.0) as u32) << 24
                | ((color.r * 255.0) as u32) << 16
                | ((color.g * 255.0) as u32) << 8
                | ((color.b * 255.0) as u32),
        };

        self.x11_renderer.fill_rectangle(
            super::renderer::Rectangle {
                x: rect.x as i16,
                y: rect.y as i16,
                width: rect.width as u16,
                height: rect.height as u16,
            },
            x11_color,
        )?;

        Ok(())
    }

    pub fn text_renderer(&self) -> &TextRenderer {
        &self.text_renderer
    }

    pub fn text_renderer_mut(&mut self) -> &mut TextRenderer {
        &mut self.text_renderer
    }

    pub fn get_cairo_context(&self) -> Result<Option<cairo::Context>> {
        if let Some(ref cairo_surface) = self.cairo_surface {
            let context = cairo::Context::new(cairo_surface)
                .with_context(|| "Failed to create Cairo context for XCB surface")?;
            Ok(Some(context))
        } else {
            Ok(None)
        }
    }

    pub fn copy_text_to_window(&mut self) -> Result<()> {
        // This method is now deprecated - we render directly to XCB surface instead
        Ok(())
    }

    pub fn flush(&self) -> Result<()> {
        self.x11_renderer.flush()?;
        if let Some(ref surface) = self.cairo_surface {
            surface.flush();
        }
        Ok(())
    }

    pub fn width(&self) -> i32 {
        self.width
    }

    pub fn height(&self) -> i32 {
        self.height
    }

    pub fn stroke_rectangle(&mut self, rect: Rect, color: Color, width: u32) -> Result<()> {
        if let Some(ref cairo_surface) = self.cairo_surface {
            let context = cairo::Context::new(cairo_surface)
                .with_context(|| "Failed to create Cairo context for stroke rectangle")?;

            context.set_source_rgba(color.r, color.g, color.b, color.a);
            context.set_line_width(width as f64);
            context.rectangle(
                rect.x as f64,
                rect.y as f64,
                rect.width as f64,
                rect.height as f64,
            );
            context
                .stroke()
                .with_context(|| "Failed to stroke rectangle")?;
        }
        Ok(())
    }

    pub fn fill_circle(
        &mut self,
        center_x: i32,
        center_y: i32,
        radius: u32,
        color: Color,
    ) -> Result<()> {
        if let Some(ref cairo_surface) = self.cairo_surface {
            let context = cairo::Context::new(cairo_surface)
                .with_context(|| "Failed to create Cairo context for fill circle")?;

            context.set_source_rgba(color.r, color.g, color.b, color.a);
            context.arc(
                center_x as f64,
                center_y as f64,
                radius as f64,
                0.0,
                2.0 * std::f64::consts::PI,
            );
            context.fill().with_context(|| "Failed to fill circle")?;
        }
        Ok(())
    }

    pub fn stroke_circle(
        &mut self,
        center_x: i32,
        center_y: i32,
        radius: u32,
        color: Color,
        width: u32,
    ) -> Result<()> {
        if let Some(ref cairo_surface) = self.cairo_surface {
            let context = cairo::Context::new(cairo_surface)
                .with_context(|| "Failed to create Cairo context for stroke circle")?;

            context.set_source_rgba(color.r, color.g, color.b, color.a);
            context.set_line_width(width as f64);
            context.arc(
                center_x as f64,
                center_y as f64,
                radius as f64,
                0.0,
                2.0 * std::f64::consts::PI,
            );
            context
                .stroke()
                .with_context(|| "Failed to stroke circle")?;
        }
        Ok(())
    }

    pub fn draw_line(
        &mut self,
        start_x: i32,
        start_y: i32,
        end_x: i32,
        end_y: i32,
        color: Color,
        width: u32,
    ) -> Result<()> {
        if let Some(ref cairo_surface) = self.cairo_surface {
            let context = cairo::Context::new(cairo_surface)
                .with_context(|| "Failed to create Cairo context for draw line")?;

            context.set_source_rgba(color.r, color.g, color.b, color.a);
            context.set_line_width(width as f64);
            context.move_to(start_x as f64, start_y as f64);
            context.line_to(end_x as f64, end_y as f64);
            context.stroke().with_context(|| "Failed to draw line")?;
        }
        Ok(())
    }

    pub fn renderer(&mut self) -> &mut Renderer<'static> {
        &mut self.x11_renderer
    }

    pub fn has_cairo_surface(&self) -> bool {
        self.cairo_surface.is_some()
    }
}