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

将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确认配置

修改后的关键逻辑示例:

  1. 更新Delegate结构体添加状态字段:
struct Delegate {
    needs_restore: bool,
    buffer: Option<WlBuffer>,
    surface: Option<WlSurface>,
}
  1. 在main函数中,执行关闭操作后设置状态:
surface.attach(None, 0, 0);
surface.commit();
delegate.needs_restore = true;
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 08:44:55