aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2025-06-15 12:37:42 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2025-07-13 12:59:01 +0300
commitce501bca9e753093170123ed62ae7bb6796a8255 (patch)
tree73652e63a8d1c00fe1cd49cedfd8133573c03720
parent45e9bb769d7bc1db89a0867484c6b560a7b25cf9 (diff)
downloadniri-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.rs231
-rw-r--r--src/tests/layer_shell.rs90
-rw-r--r--src/tests/mod.rs1
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;