//! Real v4 feedback subscriptions over a private Wayland socket; no DRM/EGL. use std::io::Read; use std::os::unix::net::UnixStream; use smithay::backend::allocator::{Format, Fourcc, Modifier}; use smithay::output::{Mode, Output, PhysicalProperties, Subpixel}; use smithay::reexports::calloop::EventLoop; use smithay::reexports::wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1::TrancheFlags; use smithay::reexports::wayland_server::Display; use smithay::wayland::dmabuf::DmabufFeedbackBuilder; use super::*; use crate::Ferese; use crate::state::{dispatch_test_client as dispatch, test_client_request as request, test_window}; fn messages(wire: &mut UnixStream) -> Vec<(u32, u32, Vec)> { wire.set_nonblocking(true).unwrap(); let mut bytes = Vec::new(); let _ = wire.read_to_end(&mut bytes); wire.set_nonblocking(false).unwrap(); let mut offset = 0; let mut result = Vec::new(); while offset < bytes.len() { let object = u32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap()); let header = u32::from_ne_bytes(bytes[offset + 4..offset + 8].try_into().unwrap()); let size = (header >> 16) as usize; result.push(( object, header & 0xffff, bytes[offset + 8..offset + size] .chunks_exact(4) .map(|word| u32::from_ne_bytes(word.try_into().unwrap())) .collect(), )); offset += size; } result } fn flags(events: &[(u32, u32, Vec)], object: u32) -> Vec { events .iter() .filter(|(id, opcode, _)| *id == object && *opcode == 6) .map(|(_, _, args)| args[0]) .collect() } fn done(events: &[(u32, u32, Vec)], object: u32) -> usize { events .iter() .filter(|(id, opcode, _)| *id == object && *opcode == 0) .count() } #[test] fn subscriptions_update_deduplicate_and_retire_device_preferences() { if !crate::startup_tests::private_runtime( "dmabuf_feedback::tests::subscriptions_update_deduplicate_and_retire_device_preferences", ) { return; } let mut events = EventLoop::try_new().unwrap(); let mut config = crate::config::Config::default().runtime_config().unwrap(); config.wallpaper.path = None; let mut state = Ferese::new(&mut events, Display::new().unwrap(), config).unwrap(); let output = Output::new( "feedback-test".into(), PhysicalProperties { size: (0, 0).into(), subpixel: Subpixel::Unknown, make: "test".into(), model: "test".into(), }, ); output.change_current_state( Some(Mode { size: (100, 100).into(), refresh: 60_000, }), None, None, None, ); state.space.map_output(&output, (0, 0)); state.register_output(&output, "feedback-test".into()); let f = Format { code: Fourcc::Argb8888, modifier: Modifier::Linear, }; let fallback = DmabufFeedbackBuilder::new(10, [f]).build().unwrap(); let preferred = DmabufFeedbackBuilder::new(10, [f]) .add_preference_tranche(20, Some(TrancheFlags::Scanout), [f]) .build() .unwrap(); let global = state .dmabuf_state .create_global_with_default_feedback::(&state.display_handle, &fallback); state.dmabuf_feedback.register(global, fallback.clone()); let (window, mut wire) = test_window(&mut state, &mut events, 0xffabcdef); let surface = window.toplevel().unwrap().wl_surface().clone(); request(&mut wire, 1, 1, &[7], None); // wl_display.get_registry dispatch(&mut events, &mut state); let registry = messages(&mut wire); let dmabuf_name = registry .iter() .find_map(|(object, opcode, args)| { if *object != 7 || *opcode != 0 { return None; } let bytes = args[2..].iter().flat_map(|word| word.to_ne_bytes()).collect::>(); bytes.starts_with(b"zwp_linux_dmabuf_v1\0").then_some(args[0]) }) .expect("DMA-BUF v4 global"); let name = b"zwp_linux_dmabuf_v1\0"; let mut padded = name.to_vec(); padded.resize(name.len().next_multiple_of(4), 0); let bind = [dmabuf_name, name.len() as u32] .into_iter() .chain( padded .chunks_exact(4) .map(|word| u32::from_ne_bytes(word.try_into().unwrap())), ) .chain([4, 8]) .collect::>(); request(&mut wire, 7, 0, &bind, None); request(&mut wire, 8, 3, &[9, 2], None); // get_surface_feedback dispatch(&mut events, &mut state); let initial = messages(&mut wire); assert_eq!(flags(&initial, 9), [0]); assert_eq!(done(&initial, 9), 1); assert_eq!(state.dmabuf_feedback.requested().count(), 1); let main = initial.iter().find(|(id, opcode, _)| *id == 9 && *opcode == 2).unwrap(); assert_eq!(main.2[0] as usize, std::mem::size_of::()); assert_eq!(main.2[1], 10, "main device remains the renderer"); let selected = HashMap::from([(surface.clone(), preferred.clone())]); state.dmabuf_feedback.refresh(&selected); state.display_handle.flush_clients().unwrap(); let changed = messages(&mut wire); assert_eq!(flags(&changed, 9), [TrancheFlags::Scanout.bits(), 0]); assert_eq!(done(&changed, 9), 1); let target = changed.iter().find(|(id, opcode, _)| *id == 9 && *opcode == 4).unwrap(); assert_eq!(target.2[1], 20, "scanout tranche targets the primary DRM device"); state.dmabuf_feedback.refresh(&selected); state.display_handle.flush_clients().unwrap(); assert!(messages(&mut wire).is_empty(), "Smithay suppresses unchanged feedback"); request(&mut wire, 8, 3, &[10, 2], None); dispatch(&mut events, &mut state); let second = messages(&mut wire); assert_eq!(flags(&second, 10), [TrancheFlags::Scanout.bits(), 0]); state.dmabuf_feedback.refresh(&HashMap::new()); // migration/ineligibility fallback state.display_handle.flush_clients().unwrap(); let fallback_events = messages(&mut wire); assert_eq!(flags(&fallback_events, 9), [0]); assert_eq!(flags(&fallback_events, 10), [0]); assert_eq!(done(&fallback_events, 9), 1); assert_eq!(done(&fallback_events, 10), 1); request(&mut wire, 9, 0, &[], None); request(&mut wire, 10, 0, &[], None); dispatch(&mut events, &mut state); messages(&mut wire); request(&mut wire, 8, 3, &[11, 2], None); request(&mut wire, 8, 2, &[12], None); // get_default_feedback dispatch(&mut events, &mut state); let renewed = messages(&mut wire); assert_eq!(flags(&renewed, 11), [0], "renewal starts with safe render feedback"); assert_eq!(flags(&renewed, 12), [0]); state.dmabuf_feedback.refresh(&selected); state.display_handle.flush_clients().unwrap(); let promoted = messages(&mut wire); assert_eq!(flags(&promoted, 11), [TrancheFlags::Scanout.bits(), 0]); state.dmabuf_feedback.remove(global); state .dmabuf_state .disable_global::(&state.display_handle, &global); state.display_handle.flush_clients().unwrap(); let retired = messages(&mut wire); assert_eq!(done(&retired, 11), 1); assert_eq!( flags(&retired, 11), [0], "retirement keeps a valid main-device render tranche" ); assert_eq!( done(&retired, 12), 0, "default renderer capabilities remain unchanged on existing bindings" ); assert!( retired .iter() .any(|(id, opcode, args)| *id == 7 && *opcode == 1 && args[0] == dmabuf_name) ); state.dmabuf_feedback.refresh(&selected); // A stale choice cannot resurrect a retired preference. state.display_handle.flush_clients().unwrap(); assert!(messages(&mut wire).is_empty()); assert!(state.dmabuf_feedback.new_surface(&surface, global).is_none()); drop(wire); dispatch(&mut events, &mut state); state.dmabuf_feedback.refresh(&HashMap::new()); assert_eq!(state.dmabuf_feedback.requested().count(), 0); }