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);
let mut attempts = Vec::new();
#[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, },
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));
}
}
match cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height) {
Ok(_image_surface) => {
println!("✓ Cairo image surface created successfully as fallback");
attempts.push("Image surface: Success (but not usable for X11)".to_string());
}
Err(e) => {
attempts.push(format!("Image surface fallback: {}", e));
}
}
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, 1,
) {
Ok(_surface) => {
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));
}
}
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<()> {
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()
}
}