Я успешно создал окно с помощью 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");
}
Когда я запускаю этот код, появляется окно, в котором строка «тест» отображается красным цветом. Когда я удаляю строку для записи поверхности в файл, окно все равно появляется, но текст не появляется.
Добро пожаловать в 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()
.
Позвонить conn.flush()
после розыгрыша/перед wait_for_event()
? Я отредактирую свой ответ, чтобы добавить это.
conn.flush()
сработало. Большое спасибо!
Спасибо! Я добавил некоторую логику в последний цикл для обработки событий раскрытия (и добавил маску событий для получения событий раскрытия), но по-прежнему ничего не отображается. Я оставил большую часть логики рисования вне цикла, поэтому единственный код, который запускается несколько раз, — это функция
show_layout
иsurface.flush()
. Есть мысли, почему это не работает? Еще раз спасибо.