test: add semantic effects layer probe

This commit is contained in:
Samuel Onoja committed 2026-09-25 04:49:24 +01:00
1 parent bf684a179b
commit b4fee6f72b
4 files changed
+256

No files matched your search

Generated
+10
View File
@@ -446,6 +446,16 @@ dependencies = [
"ferese-layout",
]
[[package]]
name = "ferese-effects-probe"
version = "0.1.0"
dependencies = [
"ferese-protocols",
"tempfile",
"wayland-client",
"wayland-protocols-wlr",
]
[[package]]
name = "ferese-ipc"
version = "0.1.0"
+3
View File
@@ -7,6 +7,7 @@ members = [
"crates/ferese-ipc",
"crates/ferese-layout",
"crates/ferese-protocols",
"tools/ferese-effects-probe",
"tools/ferese-malformed-client",
]
resolver = "3"
@@ -35,9 +36,11 @@ tracing = "0.1.41"
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
tempfile = "3.23.0"
toml = "0.9.7"
xcursor = "0.3.11"
wayland-backend = "0.3.17"
wayland-client = "0.31.15"
wayland-scanner = "0.31.11"
wayland-server = "0.31.14"
wayland-protocols-wlr = { version = "0.3.12", features = ["client"] }
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "ferese-effects-probe"
version.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
ferese-protocols = { path = "../../crates/ferese-protocols", default-features = false, features = ["client"] }
tempfile.workspace = true
wayland-client.workspace = true
wayland-protocols-wlr.workspace = true
+231
View File
@@ -0,0 +1,231 @@
use std::error::Error;
use std::io::{Seek, SeekFrom, Write};
use std::os::fd::AsFd;
use ferese_protocols::effects::v1::client::{
ferese_effects_manager_v1::FereseEffectsManagerV1,
ferese_surface_effects_v1::{self, FereseSurfaceEffectsV1},
};
use wayland_client::{
Connection, Dispatch, QueueHandle, delegate_noop,
protocol::{wl_buffer, wl_compositor, wl_registry, wl_shm, wl_shm_pool, wl_surface},
};
use wayland_protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::{self, ZwlrLayerShellV1},
zwlr_layer_surface_v1::{self, ZwlrLayerSurfaceV1},
};
const WIDTH: u32 = 640;
const HEIGHT: u32 = 64;
fn main() -> Result<(), Box<dyn Error>> {
let connection = Connection::connect_to_env()?;
let mut queue = connection.new_event_queue();
let qh = queue.handle();
connection.display().get_registry(&qh, ());
let mut state = ProbeState::default();
while state.effects.is_none() || !state.configured {
queue.blocking_dispatch(&mut state)?;
state.try_initialize(&qh)?;
if state.closed {
return Err("layer surface was closed before its first configure".into());
}
}
let effects = state.effects.clone().expect("effects initialized");
let initial_configures = state.configure_count;
for role in [
ferese_surface_effects_v1::Role::PanelElevated,
ferese_surface_effects_v1::Role::Popover,
ferese_surface_effects_v1::Role::Menu,
ferese_surface_effects_v1::Role::Notification,
ferese_surface_effects_v1::Role::Hud,
ferese_surface_effects_v1::Role::Modal,
ferese_surface_effects_v1::Role::Panel,
] {
effects.set_role(role);
queue.roundtrip(&mut state)?;
if state.configure_count != initial_configures {
return Err(format!("semantic role {role:?} changed layer geometry").into());
}
}
effects.clear_role();
queue.roundtrip(&mut state)?;
if state.configure_count != initial_configures {
return Err("clearing the semantic role changed layer geometry".into());
}
println!(
"PASS surface.panel and all semantic roles preserved {}x{} layer geometry",
state.width, state.height
);
Ok(())
}
#[derive(Default)]
struct ProbeState {
compositor: Option<wl_compositor::WlCompositor>,
shm: Option<wl_shm::WlShm>,
layer_shell: Option<ZwlrLayerShellV1>,
effects_manager: Option<FereseEffectsManagerV1>,
surface: Option<wl_surface::WlSurface>,
layer_surface: Option<ZwlrLayerSurfaceV1>,
effects: Option<FereseSurfaceEffectsV1>,
buffer: Option<wl_buffer::WlBuffer>,
configured: bool,
closed: bool,
configure_count: u32,
width: u32,
height: u32,
}
impl ProbeState {
fn try_initialize(&mut self, qh: &QueueHandle<Self>) -> Result<(), Box<dyn Error>> {
if self.surface.is_some() {
return Ok(());
}
let (Some(compositor), Some(_shm), Some(layer_shell), Some(effects_manager)) = (
self.compositor.as_ref(),
self.shm.as_ref(),
self.layer_shell.as_ref(),
self.effects_manager.as_ref(),
) else {
return Ok(());
};
let surface = compositor.create_surface(qh, ());
let layer_surface = layer_shell.get_layer_surface(
&surface,
None,
zwlr_layer_shell_v1::Layer::Top,
"ferese-effects-probe".to_owned(),
qh,
(),
);
layer_surface.set_anchor(
zwlr_layer_surface_v1::Anchor::Top
| zwlr_layer_surface_v1::Anchor::Left
| zwlr_layer_surface_v1::Anchor::Right,
);
layer_surface.set_size(WIDTH, HEIGHT);
layer_surface.set_exclusive_zone(HEIGHT as i32);
let effects = effects_manager.get_surface_effects(&surface, qh, ());
effects.set_role(ferese_surface_effects_v1::Role::Panel);
surface.commit();
self.surface = Some(surface);
self.layer_surface = Some(layer_surface);
self.effects = Some(effects);
Ok(())
}
}
fn create_buffer(
shm: &wl_shm::WlShm,
qh: &QueueHandle<ProbeState>,
width: u32,
height: u32,
) -> Result<wl_buffer::WlBuffer, Box<dyn Error>> {
let stride = width * 4;
let size = stride * height;
let mut file = tempfile::tempfile()?;
file.set_len(u64::from(size))?;
let pixel = [0x28_u8, 0x24, 0x20, 0xd8];
for _ in 0..width * height {
file.write_all(&pixel)?;
}
file.seek(SeekFrom::Start(0))?;
let pool = shm.create_pool(file.as_fd(), size as i32, qh, ());
let buffer = pool.create_buffer(
0,
width as i32,
height as i32,
stride as i32,
wl_shm::Format::Argb8888,
qh,
(),
);
pool.destroy();
Ok(buffer)
}
impl Dispatch<wl_registry::WlRegistry, ()> for ProbeState {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: wl_registry::Event,
_data: &(),
_connection: &Connection,
qh: &QueueHandle<Self>,
) {
let wl_registry::Event::Global {
name,
interface,
version,
} = event
else {
return;
};
match interface.as_str() {
"wl_compositor" => state.compositor = Some(registry.bind(name, version.min(6), qh, ())),
"wl_shm" => state.shm = Some(registry.bind(name, 1, qh, ())),
"zwlr_layer_shell_v1" => {
state.layer_shell = Some(registry.bind(name, version.min(4), qh, ()))
}
"ferese_effects_manager_v1" => {
state.effects_manager = Some(registry.bind(name, 1, qh, ()))
}
_ => {}
}
}
}
impl Dispatch<ZwlrLayerSurfaceV1, ()> for ProbeState {
fn event(
state: &mut Self,
layer_surface: &ZwlrLayerSurfaceV1,
event: zwlr_layer_surface_v1::Event,
_data: &(),
_connection: &Connection,
qh: &QueueHandle<Self>,
) {
match event {
zwlr_layer_surface_v1::Event::Configure {
serial,
width,
height,
} => {
layer_surface.ack_configure(serial);
state.configure_count = state.configure_count.saturating_add(1);
state.width = if width == 0 { WIDTH } else { width };
state.height = if height == 0 { HEIGHT } else { height };
if !state.configured {
let surface = state.surface.clone().expect("surface initialized");
let shm = state.shm.as_ref().expect("shm initialized");
let buffer = create_buffer(shm, qh, state.width, state.height)
.expect("create configured shared-memory buffer");
surface.attach(Some(&buffer), 0, 0);
surface.damage_buffer(0, 0, state.width as i32, state.height as i32);
surface.commit();
state.buffer = Some(buffer);
state.configured = true;
}
}
zwlr_layer_surface_v1::Event::Closed => state.closed = true,
_ => {}
}
}
}
delegate_noop!(ProbeState: ignore wl_compositor::WlCompositor);
delegate_noop!(ProbeState: ignore wl_shm::WlShm);
delegate_noop!(ProbeState: ignore wl_shm_pool::WlShmPool);
delegate_noop!(ProbeState: ignore wl_buffer::WlBuffer);
delegate_noop!(ProbeState: ignore wl_surface::WlSurface);
delegate_noop!(ProbeState: ignore ZwlrLayerShellV1);
delegate_noop!(ProbeState: ignore FereseEffectsManagerV1);
delegate_noop!(ProbeState: ignore FereseSurfaceEffectsV1);