Skip to main content

x11_overlay/graphics/
context.rs

1use super::renderer::Renderer;
2use super::text::TextRenderer;
3use super::{Color, Rect};
4use anyhow::{Context as AnyhowContext, Result};
5use cairo::{XCBConnection as CairoXCBConnection, XCBDrawable, XCBSurface, XCBVisualType};
6use x11rb::protocol::xproto::*;
7use x11rb::xcb_ffi::XCBConnection;
8
9pub struct GraphicsContext {
10    x11_renderer: Renderer<'static>,
11    text_renderer: TextRenderer,
12    cairo_surface: Option<XCBSurface>,
13    width: i32,
14    height: i32,
15}
16
17impl GraphicsContext {
18    pub fn new(
19        conn: &'static XCBConnection,
20        window: Window,
21        visual: &Visualtype,
22        width: i32,
23        height: i32,
24    ) -> Result<Self> {
25        let x11_renderer = Renderer::new(conn, window);
26        let text_renderer = TextRenderer::new(width, height)?;
27
28        let mut context = Self {
29            x11_renderer,
30            text_renderer,
31            cairo_surface: None,
32            width,
33            height,
34        };
35
36        context.setup_cairo_surface(conn, window, visual)?;
37        Ok(context)
38    }
39
40    fn setup_cairo_surface(
41        &mut self,
42        conn: &XCBConnection,
43        window: Window,
44        visual: &Visualtype,
45    ) -> Result<()> {
46        println!("Setting up Cairo XCB surface:");
47        println!("  Visual ID: 0x{:x}", visual.visual_id);
48        println!("  Visual class: {:?}", visual.class);
49        println!("  Bits per RGB: {}", visual.bits_per_rgb_value);
50        println!("  Colormap entries: {}", visual.colormap_entries);
51        println!("  Red mask: 0x{:x}", visual.red_mask);
52        println!("  Green mask: 0x{:x}", visual.green_mask);
53        println!("  Blue mask: 0x{:x}", visual.blue_mask);
54        println!("  Window dimensions: {}x{}", self.width, self.height);
55
56        let cairo_conn =
57            unsafe { CairoXCBConnection::from_raw_none(conn.get_raw_xcb_connection() as *mut _) };
58
59        let cairo_drawable = XCBDrawable(window);
60
61        // Try different approaches to create the visual
62        let mut attempts = Vec::new();
63
64        // Attempt 1: Proper visual structure reconstruction
65        // Create a proper xcb_visualtype_t structure from the x11rb Visualtype
66        #[repr(C)]
67        struct XcbVisualType {
68            visual_id: u32,
69            class: u8,
70            bits_per_rgb_value: u8,
71            colormap_entries: u16,
72            red_mask: u32,
73            green_mask: u32,
74            blue_mask: u32,
75            pad0: [u8; 4],
76        }
77
78        let xcb_visual = XcbVisualType {
79            visual_id: visual.visual_id,
80            class: match visual.class {
81                x11rb::protocol::xproto::VisualClass::STATIC_GRAY => 0,
82                x11rb::protocol::xproto::VisualClass::GRAY_SCALE => 1,
83                x11rb::protocol::xproto::VisualClass::STATIC_COLOR => 2,
84                x11rb::protocol::xproto::VisualClass::PSEUDO_COLOR => 3,
85                x11rb::protocol::xproto::VisualClass::TRUE_COLOR => 4,
86                x11rb::protocol::xproto::VisualClass::DIRECT_COLOR => 5,
87                _ => 4, // Default to TRUE_COLOR for unknown types
88            },
89            bits_per_rgb_value: visual.bits_per_rgb_value,
90            colormap_entries: visual.colormap_entries,
91            red_mask: visual.red_mask,
92            green_mask: visual.green_mask,
93            blue_mask: visual.blue_mask,
94            pad0: [0; 4],
95        };
96
97        let cairo_visual_1 =
98            unsafe { XCBVisualType::from_raw_none(&xcb_visual as *const XcbVisualType as *mut _) };
99
100        match XCBSurface::create(
101            &cairo_conn,
102            &cairo_drawable,
103            &cairo_visual_1,
104            self.width,
105            self.height,
106        ) {
107            Ok(surface) => {
108                self.cairo_surface = Some(surface);
109                println!("✓ Cairo XCB surface created successfully (reconstructed visual)");
110                return Ok(());
111            }
112            Err(e) => {
113                attempts.push(format!("Reconstructed visual structure: {}", e));
114            }
115        }
116
117        // Attempt 2: Try creating image surface instead of XCB surface
118        match cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height) {
119            Ok(_image_surface) => {
120                // This isn't directly usable for X11, but let's see if it creates successfully
121                println!("✓ Cairo image surface created successfully as fallback");
122                // We can't use this directly with XCB, so continue to other attempts
123                attempts.push("Image surface: Success (but not usable for X11)".to_string());
124            }
125            Err(e) => {
126                attempts.push(format!("Image surface fallback: {}", e));
127            }
128        }
129
130        // Attempt 3: Try with zero width/height to test basic functionality
131        let cairo_visual_3 =
132            unsafe { XCBVisualType::from_raw_none(&visual.visual_id as *const u32 as *mut _) };
133
134        match XCBSurface::create(
135            &cairo_conn,
136            &cairo_drawable,
137            &cairo_visual_3,
138            1, // Try with minimal dimensions
139            1,
140        ) {
141            Ok(_surface) => {
142                // If minimal surface works, try full size
143                match XCBSurface::create(
144                    &cairo_conn,
145                    &cairo_drawable,
146                    &cairo_visual_3,
147                    self.width,
148                    self.height,
149                ) {
150                    Ok(full_surface) => {
151                        self.cairo_surface = Some(full_surface);
152                        println!("✓ Cairo XCB surface created successfully (full size after minimal test)");
153                        return Ok(());
154                    }
155                    Err(e) => {
156                        attempts.push(format!("Full size after minimal: {}", e));
157                    }
158                }
159            }
160            Err(e) => {
161                attempts.push(format!("Minimal size test: {}", e));
162            }
163        }
164
165        // All attempts failed
166        eprintln!("✗ All Cairo XCB surface creation attempts failed:");
167        for (i, attempt) in attempts.iter().enumerate() {
168            eprintln!("  Attempt {}: {}", i + 1, attempt);
169        }
170        self.cairo_surface = None;
171        Ok(())
172    }
173
174    pub fn resize(&mut self, width: i32, height: i32) -> Result<()> {
175        self.width = width;
176        self.height = height;
177        self.text_renderer.resize(width, height)?;
178
179        if let Some(ref surface) = self.cairo_surface {
180            surface
181                .set_size(width, height)
182                .with_context(|| "Failed to resize Cairo surface")?;
183        }
184
185        Ok(())
186    }
187
188    pub fn clear(&mut self) -> Result<()> {
189        self.x11_renderer.clear_area(super::renderer::Rectangle {
190            x: 0,
191            y: 0,
192            width: self.width as u16,
193            height: self.height as u16,
194        })?;
195        self.text_renderer.clear();
196        Ok(())
197    }
198
199    pub fn clear_with_color(&mut self, color: Color) -> Result<()> {
200        let x11_color = super::renderer::Color {
201            argb: ((color.a * 255.0) as u32) << 24
202                | ((color.r * 255.0) as u32) << 16
203                | ((color.g * 255.0) as u32) << 8
204                | ((color.b * 255.0) as u32),
205        };
206
207        self.x11_renderer.fill_rectangle(
208            super::renderer::Rectangle {
209                x: 0,
210                y: 0,
211                width: self.width as u16,
212                height: self.height as u16,
213            },
214            x11_color,
215        )?;
216
217        self.text_renderer.clear_with_color(color);
218        Ok(())
219    }
220
221    pub fn fill_rectangle(&mut self, rect: Rect, color: Color) -> Result<()> {
222        let x11_color = super::renderer::Color {
223            argb: ((color.a * 255.0) as u32) << 24
224                | ((color.r * 255.0) as u32) << 16
225                | ((color.g * 255.0) as u32) << 8
226                | ((color.b * 255.0) as u32),
227        };
228
229        self.x11_renderer.fill_rectangle(
230            super::renderer::Rectangle {
231                x: rect.x as i16,
232                y: rect.y as i16,
233                width: rect.width as u16,
234                height: rect.height as u16,
235            },
236            x11_color,
237        )?;
238
239        Ok(())
240    }
241
242    pub fn text_renderer(&self) -> &TextRenderer {
243        &self.text_renderer
244    }
245
246    pub fn text_renderer_mut(&mut self) -> &mut TextRenderer {
247        &mut self.text_renderer
248    }
249
250    pub fn get_cairo_context(&self) -> Result<Option<cairo::Context>> {
251        if let Some(ref cairo_surface) = self.cairo_surface {
252            let context = cairo::Context::new(cairo_surface)
253                .with_context(|| "Failed to create Cairo context for XCB surface")?;
254            Ok(Some(context))
255        } else {
256            Ok(None)
257        }
258    }
259
260    pub fn copy_text_to_window(&mut self) -> Result<()> {
261        // This method is now deprecated - we render directly to XCB surface instead
262        Ok(())
263    }
264
265    pub fn flush(&self) -> Result<()> {
266        self.x11_renderer.flush()?;
267        if let Some(ref surface) = self.cairo_surface {
268            surface.flush();
269        }
270        Ok(())
271    }
272
273    pub fn width(&self) -> i32 {
274        self.width
275    }
276
277    pub fn height(&self) -> i32 {
278        self.height
279    }
280
281    pub fn stroke_rectangle(&mut self, rect: Rect, color: Color, width: u32) -> Result<()> {
282        if let Some(ref cairo_surface) = self.cairo_surface {
283            let context = cairo::Context::new(cairo_surface)
284                .with_context(|| "Failed to create Cairo context for stroke rectangle")?;
285
286            context.set_source_rgba(color.r, color.g, color.b, color.a);
287            context.set_line_width(width as f64);
288            context.rectangle(
289                rect.x as f64,
290                rect.y as f64,
291                rect.width as f64,
292                rect.height as f64,
293            );
294            context
295                .stroke()
296                .with_context(|| "Failed to stroke rectangle")?;
297        }
298        Ok(())
299    }
300
301    pub fn fill_circle(
302        &mut self,
303        center_x: i32,
304        center_y: i32,
305        radius: u32,
306        color: Color,
307    ) -> Result<()> {
308        if let Some(ref cairo_surface) = self.cairo_surface {
309            let context = cairo::Context::new(cairo_surface)
310                .with_context(|| "Failed to create Cairo context for fill circle")?;
311
312            context.set_source_rgba(color.r, color.g, color.b, color.a);
313            context.arc(
314                center_x as f64,
315                center_y as f64,
316                radius as f64,
317                0.0,
318                2.0 * std::f64::consts::PI,
319            );
320            context.fill().with_context(|| "Failed to fill circle")?;
321        }
322        Ok(())
323    }
324
325    pub fn stroke_circle(
326        &mut self,
327        center_x: i32,
328        center_y: i32,
329        radius: u32,
330        color: Color,
331        width: u32,
332    ) -> Result<()> {
333        if let Some(ref cairo_surface) = self.cairo_surface {
334            let context = cairo::Context::new(cairo_surface)
335                .with_context(|| "Failed to create Cairo context for stroke circle")?;
336
337            context.set_source_rgba(color.r, color.g, color.b, color.a);
338            context.set_line_width(width as f64);
339            context.arc(
340                center_x as f64,
341                center_y as f64,
342                radius as f64,
343                0.0,
344                2.0 * std::f64::consts::PI,
345            );
346            context
347                .stroke()
348                .with_context(|| "Failed to stroke circle")?;
349        }
350        Ok(())
351    }
352
353    pub fn draw_line(
354        &mut self,
355        start_x: i32,
356        start_y: i32,
357        end_x: i32,
358        end_y: i32,
359        color: Color,
360        width: u32,
361    ) -> Result<()> {
362        if let Some(ref cairo_surface) = self.cairo_surface {
363            let context = cairo::Context::new(cairo_surface)
364                .with_context(|| "Failed to create Cairo context for draw line")?;
365
366            context.set_source_rgba(color.r, color.g, color.b, color.a);
367            context.set_line_width(width as f64);
368            context.move_to(start_x as f64, start_y as f64);
369            context.line_to(end_x as f64, end_y as f64);
370            context.stroke().with_context(|| "Failed to draw line")?;
371        }
372        Ok(())
373    }
374
375    pub fn renderer(&mut self) -> &mut Renderer<'static> {
376        &mut self.x11_renderer
377    }
378
379    pub fn has_cairo_surface(&self) -> bool {
380        self.cairo_surface.is_some()
381    }
382}