将None附加到Surface后重新attach缓冲区出现图层表面配置错误咨询
问题描述
我需要实现弹窗关闭后重新显示的功能,尝试通过以下步骤实现:先将缓冲区(buffer)附加到WlSurface,再将None附加到该Surface,最后重新附加之前的缓冲区。但重新附加缓冲区时出现错误:layer_surface has never been configured。查阅相关文档未找到该行为的说明,请问这是对用法存在误解,还是可能存在潜在bug?
示例代码
use std::{ fs::File, io::{Seek, SeekFrom, Write}, os::fd::AsFd, }; use wayland_client::{ delegate_noop, globals::{registry_queue_init, GlobalListContents}, protocol::{ wl_buffer::WlBuffer, wl_compositor::WlCompositor, wl_output::WlOutput, wl_registry::{self, WlRegistry}, wl_shm::{Format, WlShm}, wl_shm_pool::WlShmPool, wl_surface::WlSurface, }, Connection, Dispatch, QueueHandle, }; use wayland_protocols_wlr::layer_shell::v1::client::{ zwlr_layer_shell_v1::{Layer, ZwlrLayerShellV1}, zwlr_layer_surface_v1::{self, Anchor, KeyboardInteractivity, ZwlrLayerSurfaceV1}, }; struct Delegate; impl Dispatch<WlRegistry, GlobalListContents> for Delegate { fn event( _: &mut Self, _: &WlRegistry, _event: wl_registry::Event, _: &GlobalListContents, _: &Connection, _: &QueueHandle<Self>, ) { } } impl Dispatch<ZwlrLayerSurfaceV1, ()> for Delegate { fn event( _: &mut Self, layer_surface: &ZwlrLayerSurfaceV1, event: <ZwlrLayerSurfaceV1 as wayland_client::Proxy>::Event, _: &(), _: &Connection, _: &QueueHandle<Self>, ) { println!("test"); if let zwlr_layer_surface_v1::Event::Configure { serial, .. } = event { layer_surface.ack_configure(serial); } } } delegate_noop!(Delegate: ignore WlOutput); delegate_noop!(Delegate: ignore WlShm); delegate_noop!(Delegate: ignore WlShmPool); delegate_noop!(Delegate: ignore WlBuffer); delegate_noop!(Delegate: ignore WlCompositor); delegate_noop!(Delegate: ignore WlSurface); delegate_noop!(Delegate: ignore ZwlrLayerShellV1); fn main() { let conn = Connection::connect_to_env().unwrap(); let (globals, mut event_queue) = registry_queue_init::<Delegate>(&conn).unwrap(); let qh = event_queue.handle(); let mut outputs = vec![]; for global in globals.contents().clone_list() { if let "wl_output" = &global.interface[..] { let output: WlOutput = globals .registry() .bind(global.name, global.version, &qh, ()); outputs.push(output); } } let output = &outputs[0]; let mut overlay = tempfile::tempfile().unwrap(); let shm: WlShm = globals.bind(&qh, 1..=1, ()).unwrap(); let compositor: WlCompositor = globals.bind(&qh, 1..=4, ()).unwrap(); let layer_shell: ZwlrLayerShellV1 = globals.bind(&qh, 1..=1, ()).unwrap(); let (width, height) = (1920, 1080); let shm_pool = shm.create_pool(overlay.as_fd(), width * height * 4, &qh, ()); let buffer = shm_pool.create_buffer(0, width, height, width * 4, Format::Abgr8888, &qh, ()); let surface = compositor.create_surface(&qh, ()); let layer_surface = layer_shell.get_layer_surface( &surface, Some(output), Layer::Overlay, "ScreenshotUtil".to_string(), &qh, (), ); layer_surface.set_size(width as u32, height as u32); layer_surface.set_anchor(Anchor::Bottom); layer_surface.set_margin(0, 0, 0, 0); layer_surface.set_keyboard_interactivity(KeyboardInteractivity::None); layer_surface.set_exclusive_zone(-1); draw_background([100, 44, 44, 44], &mut overlay); surface.commit(); event_queue.blocking_dispatch(&mut Delegate).unwrap(); surface.attach(Some(&buffer), 0, 0); surface.commit(); surface.attach(None, 0, 0); surface.commit(); surface.attach(Some(&buffer), 0, 0); surface.commit(); loop { event_queue.blocking_dispatch(&mut Delegate).unwrap(); } } fn draw_background(background: [u8; 4], file: &mut File) { file.seek(SeekFrom::Start(0)).unwrap(); let cashed = u32::from_ne_bytes(background); let compressed_buf = vec![cashed; 1920 * 1080]; let uncompressed_buf = bytemuck::cast_slice::<u32, u8>(&compressed_buf[..]); file.write_all(uncompressed_buf).unwrap(); }
问题原因与解决方法
这个错误是因为你在将Surface设置为无缓冲区(attach None)后,重新附加缓冲区时,没有等待服务器重新发送configure事件并完成确认。
Wayland的layer surface有严格的生命周期规则:当你修改Surface的可见性(比如从有缓冲区变为无缓冲区,再变回有缓冲区),服务器会重新触发configure事件,你必须在收到该事件后调用ack_configure,之后才能再次提交Surface的变更。
你的代码中,在执行surface.attach(None, 0, 0); surface.commit();后立刻执行了重新附加缓冲区并提交,此时服务器还没来得及发送新的configure事件,导致提交操作违反协议,触发错误。
解决步骤:
- 维护一个状态变量,标记当前Surface是否处于"需要重新显示"的状态
- 当执行
attach None并提交后,设置状态为需要重新显示 - 在
configure事件处理中,如果处于需要重新显示的状态,就重新附加缓冲区并提交,同时调用ack_configure确认配置
修改后的关键逻辑示例:
- 更新Delegate结构体添加状态字段:
struct Delegate { needs_restore: bool, buffer: Option<WlBuffer>, surface: Option<WlSurface>, }
- 在main函数中,执行关闭操作后设置状态:
surface.attach(None, 0, 0); surface.commit(); delegate.needs_restore = true;
- 在Dispatch事件处理中处理恢复逻辑:
if let zwlr_layer_surface_v1::Event::Configure { serial, .. } = event { layer_surface.ack_configure(serial); if self.needs_restore { if let (Some(surface), Some(buffer)) = (&self.surface, &self.buffer) { surface.attach(Some(buffer), 0, 0); surface.commit(); } self.needs_restore = false; } }
另外,注意你最初的surface.commit()在设置layer surface属性后执行,这会触发第一次configure事件,你的代码已经正确处理了这次确认,但后续的可见性变更需要重复这个流程。
内容的提问来源于stack exchange,提问作者Laycookie
相关产品推荐
相关产品推荐

