Cairo XCB Surface仅在写入PNG时通过Pango渲染文本问题
问题描述
通过xcb-rs成功创建窗口,但使用pangocairo向窗口写入文本时,仅在将Cairo XCB Surface写入PNG文件时才会渲染文本;若尝试用Cairo直接绘制矩形,则完全无法渲染。
运行环境:Arch Linux(内核6.9.2)、xorg-server 21.1.13、i3 4.23、picom 11.2。
精简后的代码:
use anyhow::Result; use cairo::XCBConnection; use temp::xreq::*; use xcb::{composite, x, Xid}; fn main() -> Result<()> { // generate necessary ids, set up constants let (conn, screen_idx) = xcb::Connection::connect(None)?; let window: x::Window = conn.generate_id(); let colormap: x::Colormap = conn.generate_id(); let screen = conn.get_setup().roots().nth(screen_idx as usize).unwrap(); let width = screen.width_in_pixels(); let height = 32; let depth = 32; let mut visual = find_visual(screen, depth); // create colormap conn.check_request(conn.send_request_checked(&x::CreateColormap { alloc: x::ColormapAlloc::None, mid: colormap, visual: visual.visual_id(), window: screen.root(), }))?; // create window conn.check_request(conn.send_request_checked(&x::CreateWindow { depth, wid: window, parent: screen.root(), x: 0, y: 0, width, height, border_width: 0, class: x::WindowClass::InputOutput, visual: visual.visual_id(), value_list: &[ x::Cw::BackPixel(0x00000000), x::Cw::BorderPixel(0x00000000), x::Cw::Colormap(colormap), ], }))?; // map window conn.check_request(conn.send_request_checked(&x::MapWindow { window }))?; // create cairo surface for the window let surface = cairo::XCBSurface::create( unsafe { &XCBConnection::from_raw_none(std::mem::transmute(conn.get_raw_conn())) }, &cairo::XCBDrawable(window.resource_id()), unsafe { &cairo::XCBVisualType::from_raw_none(std::mem::transmute(&mut visual as *mut _)) }, width.into(), height.into(), )?; // draw text and write image to png let context = cairo::Context::new(&surface)?; let pango_context = pangocairo::pango::Context::new(); pango_context.set_font_map(Some(&pangocairo::FontMap::new())); pango_context.load_font(&pangocairo::pango::FontDescription::from_string( "Fira Code", )); context.set_source_rgba(1.0, 0.0, 0.0, 1.0); let layout = pangocairo::pango::Layout::new(&pango_context); layout.set_text("test"); pangocairo::functions::show_layout(&context, &layout); let mut file = std::fs::File::create("/dev/null")?; surface.write_to_png(&mut file)?; // prevent program termination loop {} }
引用的工具函数:
use std::fmt::Debug; use anyhow::{Context, Result}; use xcb::x::{self, Atom}; pub fn send<X: xcb::RequestWithoutReply + Debug>(conn: &xcb::Connection, req: &X) -> Result<()> { Ok(conn .check_request(conn.send_request_checked(req)) .with_context(|| format!("sending xcb request failed: {:?}", req))?) } pub fn query<X: xcb::RequestWithReply + Debug>( conn: &xcb::Connection, req: &X, ) -> Result<<<X as xcb::Request>::Cookie as xcb::CookieWithReplyChecked>::Reply> where <X as xcb::Request>::Cookie: xcb::CookieWithReplyChecked, { Ok(conn .wait_for_reply(conn.send_request(req)) .with_context(|| format!("sending xcb query failed: {:?}", req))?) } pub fn intern_named_atom(conn: &xcb::Connection, atom: &[u8]) -> Result<xcb::x::Atom> { Ok(conn .wait_for_reply(conn.send_request(&x::InternAtom { only_if_exists: true, name: atom, }))? .atom()) } pub fn change_property<P: x::PropEl>( conn: &xcb::Connection, window: x::Window, property: Atom, r#type: Atom, data: &[P], ) -> Result<()> { Ok(conn .check_request(conn.send_request_checked(&x::ChangeProperty { mode: x::PropMode::Replace, window, property, r#type, data, })) .with_context(|| format!("changing property failed: {:?}", property))?) } pub fn change_window_property<P: x::PropEl>( conn: &xcb::Connection, window: x::Window, property: Atom, data: &[P], ) -> Result<()> { change_property(conn, window, property, x::ATOM_ATOM, data) } pub fn find_visual(screen: &x::Screen, depth: u8) -> x::Visualtype { for allowed_depth in screen.allowed_depths() { if allowed_depth.depth() == depth { for visual in allowed_depth.visuals() { if visual.class() == x::VisualClass::TrueColor { return *visual; } } } } panic!("No visual type found"); }
运行现象:保留写入PNG的代码时,窗口显示并渲染红色"test"文本;移除该代码后,窗口仍显示,但文本不再渲染。
问题分析与解决
核心原因
Cairo针对XCB Surface的绘制操作默认是缓冲式的,不会自动将绘制内容同步到X服务器的窗口缓冲区中。surface.write_to_png方法执行时会强制触发Cairo的缓冲区刷新,间接把绘制内容提交到了X服务器,所以文本能显示;直接绘制矩形或移除PNG写入代码时,没有触发刷新逻辑,导致内容无法渲染到窗口。
解决方法
需要手动触发Cairo缓冲区刷新,并通知X服务器更新窗口区域,同时替换死循环为事件循环以处理X服务器事件:
修改后的关键代码片段
// ... 原有绘制代码 ... pangocairo::functions::show_layout(&context, &layout); // 1. 刷新Cairo上下文,强制提交绘制操作 context.flush(); // 2. 通知X服务器标记窗口区域需要重绘 conn.send_request(&x::ClearArea { expose: true, window, x: 0, y: 0, width, height, }); // 3. 刷新XCB连接,确保请求发送到服务器 conn.flush(); // 替换死循环为事件循环,处理X服务器事件 loop { match conn.wait_for_event() { Ok(_) => {}, Err(_) => break, } }
额外注意事项
- 绘制矩形时,同样需要执行
context.flush()和X服务器刷新操作,才能让矩形显示出来。 - 如果使用picom等合成器,确保窗口属性设置正确(比如状态栏窗口设置
_NET_WM_WINDOW_TYPE_DOCK),避免合成器拦截或延迟绘制。
内容的提问来源于stack exchange,提问作者qelxiros
相关产品推荐
相关产品推荐

