mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
tests: Add layer-shell scaffolding and an overflow test
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -6,5 +6,6 @@ mod server;
|
||||
|
||||
mod floating;
|
||||
mod fullscreen;
|
||||
mod layer_shell;
|
||||
mod transactions;
|
||||
mod window_opening;
|
||||
|
||||
Reference in New Issue
Block a user