use std::error::Error; use std::io::{Seek, SeekFrom, Write}; use std::os::fd::AsFd; use std::time::Duration; 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> { let arguments = std::env::args_os().collect::>(); let expect_hidden = arguments .iter() .any(|argument| argument == "--expect-hidden"); let preview = arguments.iter().any(|argument| argument == "--preview"); 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 { preview, ..ProbeState::default() }; if expect_hidden { queue.roundtrip(&mut state)?; if state.effects_manager.is_some() { return Err("public client could see the private effects global".into()); } println!("PASS public client cannot see ferese_effects_manager_v1"); return Ok(()); } 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()); } } if preview { effects.set_role(ferese_surface_effects_v1::Role::Panel); queue.roundtrip(&mut state)?; println!("PREVIEW glass panel is mapped over the patterned card; press Ctrl+C to stop"); while !state.closed { queue.blocking_dispatch(&mut state)?; } return Ok(()); } // Keep the final glass role mapped long enough for the compositor to run // at least one presentation. This turns shader and capture failures into // probe failures instead of disconnecting before the first rendered frame. std::thread::sleep(Duration::from_millis(100)); 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, shm: Option, layer_shell: Option, effects_manager: Option, surface: Option, layer_surface: Option, effects: Option, buffer: Option, preview_backdrop_surface: Option, preview_backdrop_layer: Option, preview_backdrop_buffer: Option, preview: bool, configured: bool, closed: bool, configure_count: u32, width: u32, height: u32, } impl ProbeState { fn try_initialize(&mut self, qh: &QueueHandle) -> Result<(), Box> { 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 = if self.preview { zwlr_layer_shell_v1::Layer::Overlay } else { zwlr_layer_shell_v1::Layer::Top }; let layer_surface = layer_shell.get_layer_surface( &surface, None, layer, "ferese-effects-probe".to_owned(), qh, (), ); if self.preview { layer_surface.set_anchor( zwlr_layer_surface_v1::Anchor::Top | zwlr_layer_surface_v1::Anchor::Right, ); layer_surface.set_margin(32, 32, 0, 0); layer_surface.set_size(480, 220); layer_surface.set_exclusive_zone(0); } else { 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(); if self.preview { let backdrop_surface = compositor.create_surface(qh, ()); let backdrop_layer = layer_shell.get_layer_surface( &backdrop_surface, None, zwlr_layer_shell_v1::Layer::Top, "ferese-effects-preview-backdrop".to_owned(), qh, (), ); backdrop_layer.set_anchor( zwlr_layer_surface_v1::Anchor::Top | zwlr_layer_surface_v1::Anchor::Right, ); backdrop_layer.set_margin(12, 12, 0, 0); backdrop_layer.set_size(520, 260); backdrop_layer.set_exclusive_zone(0); backdrop_surface.commit(); self.preview_backdrop_surface = Some(backdrop_surface); self.preview_backdrop_layer = Some(backdrop_layer); } self.surface = Some(surface); self.layer_surface = Some(layer_surface); self.effects = Some(effects); Ok(()) } } fn create_buffer( shm: &wl_shm::WlShm, qh: &QueueHandle, width: u32, height: u32, preview: bool, ) -> Result> { let stride = width * 4; let size = stride * height; let mut file = tempfile::tempfile()?; file.set_len(u64::from(size))?; let alpha = if preview { 0x28 } else { 0xd8 }; let pixel = [0x28_u8, 0x24, 0x20, alpha]; 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) } fn create_preview_backdrop_buffer( shm: &wl_shm::WlShm, qh: &QueueHandle, width: u32, height: u32, ) -> Result> { let stride = width * 4; let size = stride * height; let mut file = tempfile::tempfile()?; file.set_len(u64::from(size))?; for y in 0..height { for x in 0..width { let checker = (x / 11 + y / 11) % 2 == 0; let stripe = (x / 53) % 3; let pixel = match (checker, stripe) { (true, 0) => [0xf0, 0xf0, 0xf0, 0xff], (false, 0) => [0x08, 0x08, 0x08, 0xff], (true, 1) => [0x30, 0xe8, 0x40, 0xff], (false, 1) => [0x10, 0x18, 0x08, 0xff], (true, _) => [0x30, 0x60, 0xf0, 0xff], (false, _) => [0x18, 0x08, 0x20, 0xff], }; 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 for ProbeState { fn event( state: &mut Self, registry: &wl_registry::WlRegistry, event: wl_registry::Event, _data: &(), _connection: &Connection, qh: &QueueHandle, ) { 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 for ProbeState { fn event( state: &mut Self, layer_surface: &ZwlrLayerSurfaceV1, event: zwlr_layer_surface_v1::Event, _data: &(), _connection: &Connection, qh: &QueueHandle, ) { match event { zwlr_layer_surface_v1::Event::Configure { serial, width, height, } => { layer_surface.ack_configure(serial); if state.preview_backdrop_layer.as_ref() == Some(layer_surface) { let width = if width == 0 { 520 } else { width }; let height = if height == 0 { 260 } else { height }; let surface = state .preview_backdrop_surface .clone() .expect("preview backdrop surface initialized"); let shm = state.shm.as_ref().expect("shm initialized"); let buffer = create_preview_backdrop_buffer(shm, qh, width, height) .expect("create preview backdrop buffer"); surface.attach(Some(&buffer), 0, 0); surface.damage_buffer(0, 0, width as i32, height as i32); surface.commit(); state.preview_backdrop_buffer = Some(buffer); return; } 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, state.preview) .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);