Поверхность Cairo XCB отображает текст с панго только при записи в png

Я успешно создал окно с помощью xcb-rs. Когда я пытаюсь написать текст в это окно с помощью pangocairo, он отображается только в том случае, если я записываю поверхность в png. Если я попытаюсь, например, нарисовать прямоугольник с помощью cairo (не используя pango), он вообще не отрисовывается.

Я запускаю это в Arch Linux с версией Linux 6.9.2, xorg-server 21.1.13, i3 4.23 и picom (для прозрачности) 11.2.

Мой код многословен, потому что XCB многословен, но это настолько минимально, насколько я могу это сделать.

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");
}

Когда я запускаю этот код, появляется окно, в котором строка «тест» отображается красным цветом. Когда я удаляю строку для записи поверхности в файл, окно все равно появляется, но текст не появляется.

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
0
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Добро пожаловать в X11, где вам придется перерисовывать окно всякий раз, когда вам прикажут. Для каждого из этих случаев вы получаете событие Expose. На самом деле я не пробовал (извините), но уверен, что проблема в этом фрагменте кода:

loop {}

Вместо этого вам нужно что-то вроде:

while let Some(event) = conn.wait_for_event() {
   if xcb::EXPOSE == event.response_type() & !0x80 {
      // Redraw the window contents.
      // If you want to get fancy, you can look at the details of the event
      // and either only redraw the requested area or only deal with expose
      // events with count == 0
   }
   conn.flush();
}

Почему для вас это приводит к тому, что никакого рисунка на самом деле не видно? Итак, у вас есть состояние гонки: когда ваша программа отправляет запрос MapWindow, окно не сразу становится видимым. Вместо этого оконный менеджер получает информацию об этом и «организует действия» (например, добавляет заголовок). Только после этого окно становится видимым. Но поскольку ваша программа сразу же рисует в окне, она рисует в невидимом окне. В этом случае сервер X11 просто отбрасывает команды рисования и ничего с ними не делает.

Другая проблема заключается в том, что вам нужно вызвать surface.flush(), чтобы cairo действительно отправляла запросы на рисование на сервер X11... иногда. В 90% случаев вы уйдете, не сделав этого. А еще это зависит от возможностей сервера X11, поэтому невыполнение этого — распространенная ошибка.

Что происходит: если вы выполняете некоторые команды рисования, которые не могут быть выполнены сервером X11, cairo в основном загружает снимок экрана и выполняет рисование локально в изображении в памяти. Поскольку постоянная загрузка/загрузка этого файла обходится дорого, изображение в памяти сохраняется до тех пор, пока вы не flush() выйдете на поверхность.

Еще одна вещь, которую вам нужно: после того, как cairo сгенерирует свои запросы X11, вам необходимо убедиться, что xcb действительно отправляет их на сервер X11, а не просто хранит их в своем выходном буфере. Это то, что делает conn.flush().

Спасибо! Я добавил некоторую логику в последний цикл для обработки событий раскрытия (и добавил маску событий для получения событий раскрытия), но по-прежнему ничего не отображается. Я оставил большую часть логики рисования вне цикла, поэтому единственный код, который запускается несколько раз, — это функция show_layout и surface.flush(). Есть мысли, почему это не работает? Еще раз спасибо.

qelxiros 01.06.2024 19:42

Позвонить conn.flush() после розыгрыша/перед wait_for_event()? Я отредактирую свой ответ, чтобы добавить это.

Uli Schlachter 05.06.2024 18:35
conn.flush() сработало. Большое спасибо!
qelxiros 06.06.2024 05:16

Другие вопросы по теме