mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +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_surface::{self, XdgSurface};
|
||||||
use smithay::reexports::wayland_protocols::xdg::shell::client::xdg_toplevel::{self, XdgToplevel};
|
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::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_backend::client::Backend;
|
||||||
use wayland_client::globals::Global;
|
use wayland_client::globals::Global;
|
||||||
use wayland_client::protocol::wl_buffer::{self, WlBuffer};
|
use wayland_client::protocol::wl_buffer::{self, WlBuffer};
|
||||||
@@ -46,10 +52,12 @@ pub struct State {
|
|||||||
|
|
||||||
pub compositor: Option<WlCompositor>,
|
pub compositor: Option<WlCompositor>,
|
||||||
pub xdg_wm_base: Option<XdgWmBase>,
|
pub xdg_wm_base: Option<XdgWmBase>,
|
||||||
|
pub layer_shell: Option<ZwlrLayerShellV1>,
|
||||||
pub spbm: Option<WpSinglePixelBufferManagerV1>,
|
pub spbm: Option<WpSinglePixelBufferManagerV1>,
|
||||||
pub viewporter: Option<WpViewporter>,
|
pub viewporter: Option<WpViewporter>,
|
||||||
|
|
||||||
pub windows: Vec<Window>,
|
pub windows: Vec<Window>,
|
||||||
|
pub layers: Vec<LayerSurface>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
@@ -67,6 +75,19 @@ pub struct Window {
|
|||||||
pub configures_looked_at: usize,
|
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)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct Configure {
|
pub struct Configure {
|
||||||
pub size: (i32, i32),
|
pub size: (i32, i32),
|
||||||
@@ -74,6 +95,30 @@ pub struct Configure {
|
|||||||
pub states: Vec<xdg_toplevel::State>,
|
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)]
|
#[derive(Default)]
|
||||||
pub struct SyncData {
|
pub struct SyncData {
|
||||||
pub done: AtomicBool,
|
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 {
|
impl Client {
|
||||||
pub fn new(stream: UnixStream) -> Self {
|
pub fn new(stream: UnixStream) -> Self {
|
||||||
let id = ClientId::next();
|
let id = ClientId::next();
|
||||||
@@ -126,9 +178,11 @@ impl Client {
|
|||||||
outputs: HashMap::new(),
|
outputs: HashMap::new(),
|
||||||
compositor: None,
|
compositor: None,
|
||||||
xdg_wm_base: None,
|
xdg_wm_base: None,
|
||||||
|
layer_shell: None,
|
||||||
spbm: None,
|
spbm: None,
|
||||||
viewporter: None,
|
viewporter: None,
|
||||||
windows: Vec::new(),
|
windows: Vec::new(),
|
||||||
|
layers: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -162,6 +216,19 @@ impl Client {
|
|||||||
self.state.window(surface)
|
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 {
|
pub fn output(&mut self, name: &str) -> WlOutput {
|
||||||
self.state
|
self.state
|
||||||
.outputs
|
.outputs
|
||||||
@@ -209,6 +276,45 @@ impl State {
|
|||||||
.find(|w| w.surface == *surface)
|
.find(|w| w.surface == *surface)
|
||||||
.unwrap()
|
.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 {
|
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 {
|
impl Dispatch<WlCallback, Arc<SyncData>> for State {
|
||||||
fn event(
|
fn event(
|
||||||
_state: &mut Self,
|
_state: &mut Self,
|
||||||
@@ -306,6 +489,9 @@ impl Dispatch<WlRegistry, ()> for State {
|
|||||||
} else if interface == XdgWmBase::interface().name {
|
} else if interface == XdgWmBase::interface().name {
|
||||||
let version = min(version, XdgWmBase::interface().version);
|
let version = min(version, XdgWmBase::interface().version);
|
||||||
state.xdg_wm_base = Some(registry.bind(name, version, qh, ()));
|
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 {
|
} else if interface == WpSinglePixelBufferManagerV1::interface().name {
|
||||||
let version = min(version, WpSinglePixelBufferManagerV1::interface().version);
|
let version = min(version, WpSinglePixelBufferManagerV1::interface().version);
|
||||||
state.spbm = Some(registry.bind(name, version, qh, ()));
|
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 {
|
impl Dispatch<WlSurface, ()> for State {
|
||||||
fn event(
|
fn event(
|
||||||
_state: &mut Self,
|
_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 {
|
impl Dispatch<WlBuffer, ()> for State {
|
||||||
fn event(
|
fn event(
|
||||||
_state: &mut Self,
|
_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 floating;
|
||||||
mod fullscreen;
|
mod fullscreen;
|
||||||
|
mod layer_shell;
|
||||||
mod transactions;
|
mod transactions;
|
||||||
mod window_opening;
|
mod window_opening;
|
||||||
|
|||||||
Reference in New Issue
Block a user