diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2025-06-15 12:37:42 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-07-13 12:59:01 +0300 |
| commit | ce501bca9e753093170123ed62ae7bb6796a8255 (patch) | |
| tree | 73652e63a8d1c00fe1cd49cedfd8133573c03720 | |
| parent | 45e9bb769d7bc1db89a0867484c6b560a7b25cf9 (diff) | |
| download | niri-ce501bca9e753093170123ed62ae7bb6796a8255.tar.gz niri-ce501bca9e753093170123ed62ae7bb6796a8255.tar.bz2 niri-ce501bca9e753093170123ed62ae7bb6796a8255.zip | |
tests: Add layer-shell scaffolding and an overflow test
| -rw-r--r-- | src/tests/client.rs | 231 | ||||
| -rw-r--r-- | src/tests/layer_shell.rs | 90 | ||||
| -rw-r--r-- | src/tests/mod.rs | 1 |
3 files changed, 322 insertions, 0 deletions
diff --git a/src/tests/client.rs b/src/tests/client.rs index fff435c1..c56b7678 100644 --- a/src/tests/client.rs +++ b/src/tests/client.rs @@ -16,6 +16,12 @@ use smithay::reexports::wayland_protocols::wp::viewporter::client::wp_viewporter use smithay::reexports::wayland_protocols::xdg::shell::client::xdg_surface::{self, XdgSurface}; use smithay::reexports::wayland_protocols::xdg::shell::client::xdg_toplevel::{self, XdgToplevel}; use smithay::reexports::wayland_protocols::xdg::shell::client::xdg_wm_base::{self, XdgWmBase}; +use smithay::reexports::wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::{ + self, ZwlrLayerShellV1, +}; +use smithay::reexports::wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::{ + self, ZwlrLayerSurfaceV1, +}; use wayland_backend::client::Backend; use wayland_client::globals::Global; use wayland_client::protocol::wl_buffer::{self, WlBuffer}; @@ -46,10 +52,12 @@ pub struct State { pub compositor: Option<WlCompositor>, pub xdg_wm_base: Option<XdgWmBase>, + pub layer_shell: Option<ZwlrLayerShellV1>, pub spbm: Option<WpSinglePixelBufferManagerV1>, pub viewporter: Option<WpViewporter>, pub windows: Vec<Window>, + pub layers: Vec<LayerSurface>, } pub struct Window { @@ -67,6 +75,19 @@ pub struct Window { pub configures_looked_at: usize, } +pub struct LayerSurface { + pub qh: QueueHandle<State>, + pub spbm: WpSinglePixelBufferManagerV1, + + pub surface: WlSurface, + pub layer_surface: ZwlrLayerSurfaceV1, + pub viewport: WpViewport, + pub configures_received: Vec<(u32, LayerConfigure)>, + pub close_requested: bool, + + pub configures_looked_at: usize, +} + #[derive(Debug, Clone, Default)] pub struct Configure { pub size: (i32, i32), @@ -74,6 +95,30 @@ pub struct Configure { pub states: Vec<xdg_toplevel::State>, } +#[derive(Debug, Clone, Copy)] +pub struct LayerConfigure { + pub size: (u32, u32), +} + +#[derive(Clone, Copy, Default)] +pub struct LayerMargin { + pub top: i32, + pub right: i32, + pub bottom: i32, + pub left: i32, +} + +#[derive(Clone, Copy, Default)] +pub struct LayerConfigureProps { + pub size: Option<(u32, u32)>, + pub anchor: Option<zwlr_layer_surface_v1::Anchor>, + pub exclusive_zone: Option<i32>, + pub margin: Option<LayerMargin>, + pub kb_interactivity: Option<zwlr_layer_surface_v1::KeyboardInteractivity>, + pub layer: Option<zwlr_layer_shell_v1::Layer>, + pub exclusive_edge: Option<zwlr_layer_surface_v1::Anchor>, +} + #[derive(Default)] pub struct SyncData { pub done: AtomicBool, @@ -103,6 +148,13 @@ impl fmt::Display for Configure { } } +impl fmt::Display for LayerConfigure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "size: {} × {}", self.size.0, self.size.1)?; + Ok(()) + } +} + impl Client { pub fn new(stream: UnixStream) -> Self { let id = ClientId::next(); @@ -126,9 +178,11 @@ impl Client { outputs: HashMap::new(), compositor: None, xdg_wm_base: None, + layer_shell: None, spbm: None, viewporter: None, windows: Vec::new(), + layers: Vec::new(), }; Self { @@ -162,6 +216,19 @@ impl Client { self.state.window(surface) } + pub fn create_layer( + &mut self, + output: Option<&WlOutput>, + layer: zwlr_layer_shell_v1::Layer, + namespace: &str, + ) -> &mut LayerSurface { + self.state.create_layer(output, layer, namespace.to_owned()) + } + + pub fn layer(&mut self, surface: &WlSurface) -> &mut LayerSurface { + self.state.layer(surface) + } + pub fn output(&mut self, name: &str) -> WlOutput { self.state .outputs @@ -209,6 +276,45 @@ impl State { .find(|w| w.surface == *surface) .unwrap() } + + pub fn create_layer( + &mut self, + output: Option<&WlOutput>, + layer: zwlr_layer_shell_v1::Layer, + namespace: String, + ) -> &mut LayerSurface { + let compositor = self.compositor.as_ref().unwrap(); + let layer_shell = self.layer_shell.as_ref().unwrap(); + let viewporter = self.viewporter.as_ref().unwrap(); + + let surface = compositor.create_surface(&self.qh, ()); + let layer_surface = + layer_shell.get_layer_surface(&surface, output, layer, namespace, &self.qh, ()); + let viewport = viewporter.get_viewport(&surface, &self.qh, ()); + + let layer_surface = LayerSurface { + qh: self.qh.clone(), + spbm: self.spbm.clone().unwrap(), + + surface, + layer_surface, + viewport, + configures_received: Vec::new(), + close_requested: false, + + configures_looked_at: 0, + }; + + self.layers.push(layer_surface); + self.layers.last_mut().unwrap() + } + + pub fn layer(&mut self, surface: &WlSurface) -> &mut LayerSurface { + self.layers + .iter_mut() + .find(|w| w.surface == *surface) + .unwrap() + } } impl Window { @@ -269,6 +375,83 @@ impl Window { } } +impl LayerSurface { + pub fn commit(&self) { + self.surface.commit(); + } + + pub fn ack_last(&self) { + let serial = self.configures_received.last().unwrap().0; + self.layer_surface.ack_configure(serial); + } + + pub fn ack_last_and_commit(&self) { + self.ack_last(); + self.commit(); + } + + pub fn set_configure_props(&self, props: LayerConfigureProps) { + let LayerConfigureProps { + size, + anchor, + exclusive_zone, + margin, + kb_interactivity, + layer, + exclusive_edge, + } = props; + + if let Some(x) = size { + self.layer_surface.set_size(x.0, x.1); + } + if let Some(x) = anchor { + self.layer_surface.set_anchor(x); + } + if let Some(x) = exclusive_zone { + self.layer_surface.set_exclusive_zone(x); + } + if let Some(x) = margin { + self.layer_surface + .set_margin(x.top, x.right, x.bottom, x.left); + } + if let Some(x) = kb_interactivity { + self.layer_surface.set_keyboard_interactivity(x); + } + if let Some(x) = layer { + self.layer_surface.set_layer(x); + } + if let Some(x) = exclusive_edge { + self.layer_surface.set_exclusive_edge(x); + } + } + + pub fn attach_new_buffer(&self) { + let buffer = self.spbm.create_u32_rgba_buffer(0, 0, 0, 0, &self.qh, ()); + self.surface.attach(Some(&buffer), 0, 0); + } + + pub fn set_size(&self, w: u16, h: u16) { + self.viewport.set_destination(i32::from(w), i32::from(h)); + } + + pub fn recent_configures(&mut self) -> impl Iterator<Item = &LayerConfigure> { + let start = self.configures_looked_at; + self.configures_looked_at = self.configures_received.len(); + self.configures_received[start..].iter().map(|(_, c)| c) + } + + pub fn format_recent_configures(&mut self) -> String { + let mut buf = String::new(); + for configure in self.recent_configures() { + if !buf.is_empty() { + buf.push('\n'); + } + write!(buf, "{configure}").unwrap(); + } + buf + } +} + impl Dispatch<WlCallback, Arc<SyncData>> for State { fn event( _state: &mut Self, @@ -306,6 +489,9 @@ impl Dispatch<WlRegistry, ()> for State { } else if interface == XdgWmBase::interface().name { let version = min(version, XdgWmBase::interface().version); state.xdg_wm_base = Some(registry.bind(name, version, qh, ())); + } else if interface == ZwlrLayerShellV1::interface().name { + let version = min(version, ZwlrLayerShellV1::interface().version); + state.layer_shell = Some(registry.bind(name, version, qh, ())); } else if interface == WpSinglePixelBufferManagerV1::interface().name { let version = min(version, WpSinglePixelBufferManagerV1::interface().version); state.spbm = Some(registry.bind(name, version, qh, ())); @@ -385,6 +571,19 @@ impl Dispatch<XdgWmBase, ()> for State { } } +impl Dispatch<ZwlrLayerShellV1, ()> for State { + fn event( + _state: &mut Self, + _proxy: &ZwlrLayerShellV1, + _event: <ZwlrLayerShellV1 as wayland_client::Proxy>::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle<Self>, + ) { + unreachable!() + } +} + impl Dispatch<WlSurface, ()> for State { fn event( _state: &mut Self, @@ -470,6 +669,38 @@ impl Dispatch<XdgToplevel, ()> for State { } } +impl Dispatch<ZwlrLayerSurfaceV1, ()> for State { + fn event( + state: &mut Self, + layer_surface: &ZwlrLayerSurfaceV1, + event: <ZwlrLayerSurfaceV1 as wayland_client::Proxy>::Event, + _data: &(), + _conn: &Connection, + _qhandle: &QueueHandle<Self>, + ) { + let layer_surface = state + .layers + .iter_mut() + .find(|w| w.layer_surface == *layer_surface) + .unwrap(); + + match event { + zwlr_layer_surface_v1::Event::Configure { + serial, + width, + height, + } => { + let configure = LayerConfigure { + size: (width, height), + }; + layer_surface.configures_received.push((serial, configure)); + } + zwlr_layer_surface_v1::Event::Closed => layer_surface.close_requested = true, + _ => unreachable!(), + } + } +} + impl Dispatch<WlBuffer, ()> for State { fn event( _state: &mut Self, diff --git a/src/tests/layer_shell.rs b/src/tests/layer_shell.rs new file mode 100644 index 00000000..65014942 --- /dev/null +++ b/src/tests/layer_shell.rs @@ -0,0 +1,90 @@ +use insta::assert_snapshot; +use smithay::reexports::wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_shell_v1::Layer; +use smithay::reexports::wayland_protocols_wlr::layer_shell::v1::client::zwlr_layer_surface_v1::Anchor; + +use super::*; +use crate::tests::client::{LayerConfigureProps, LayerMargin}; + +#[test] +fn simple_top_anchor() { + let mut f = Fixture::new(); + f.add_output(1, (1920, 1080)); + let id = f.add_client(); + + let layer = f.client(id).create_layer(None, Layer::Top, ""); + let surface = layer.surface.clone(); + layer.set_configure_props(LayerConfigureProps { + anchor: Some(Anchor::Left | Anchor::Right | Anchor::Top), + size: Some((0, 50)), + ..Default::default() + }); + layer.commit(); + f.roundtrip(id); + + let layer = f.client(id).layer(&surface); + layer.attach_new_buffer(); + layer.set_size(100, 100); + layer.ack_last_and_commit(); + f.double_roundtrip(id); + + let layer = f.client(id).layer(&surface); + assert_snapshot!(layer.format_recent_configures(), @"size: 1920 × 50"); +} + +#[test] +fn margin_overflow() { + let mut f = Fixture::new(); + f.add_output(1, (1920, 1080)); + let id = f.add_client(); + + let layer = f.client(id).create_layer(None, Layer::Top, ""); + let surface = layer.surface.clone(); + layer.set_configure_props(LayerConfigureProps { + anchor: Some(Anchor::Left | Anchor::Right | Anchor::Top | Anchor::Bottom), + margin: Some(LayerMargin { + top: i32::MAX, + right: i32::MAX, + bottom: i32::MAX, + left: i32::MAX, + }), + exclusive_zone: Some(i32::MAX), + ..Default::default() + }); + layer.commit(); + f.roundtrip(id); + + let layer = f.client(id).layer(&surface); + layer.attach_new_buffer(); + layer.set_size(100, 100); + layer.ack_last_and_commit(); + f.double_roundtrip(id); + + let layer = f.client(id).layer(&surface); + assert_snapshot!(layer.format_recent_configures(), @"size: 0 × 0"); + + // Add a second one for good measure. + let layer = f.client(id).create_layer(None, Layer::Top, ""); + let surface = layer.surface.clone(); + layer.set_configure_props(LayerConfigureProps { + anchor: Some(Anchor::Left | Anchor::Right | Anchor::Top | Anchor::Bottom), + margin: Some(LayerMargin { + top: i32::MAX, + right: i32::MAX, + bottom: i32::MAX, + left: i32::MAX, + }), + exclusive_zone: Some(i32::MAX), + ..Default::default() + }); + layer.commit(); + f.roundtrip(id); + + let layer = f.client(id).layer(&surface); + layer.attach_new_buffer(); + layer.set_size(100, 100); + layer.ack_last_and_commit(); + f.double_roundtrip(id); + + let layer = f.client(id).layer(&surface); + assert_snapshot!(layer.format_recent_configures(), @"size: 0 × 0"); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index fdbb41ac..d57ce22b 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -6,5 +6,6 @@ mod server; mod floating; mod fullscreen; +mod layer_shell; mod transactions; mod window_opening; |
