You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 04:17:18