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 let mut attempts = Vec::new();
63
64 #[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, },
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 match cairo::ImageSurface::create(cairo::Format::ARgb32, self.width, self.height) {
119 Ok(_image_surface) => {
120 println!("✓ Cairo image surface created successfully as fallback");
122 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 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, 1,
140 ) {
141 Ok(_surface) => {
142 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 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 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}