//! Test natif (CachyOS, hors QEMU) : wl_subcompositor protocole (phase //! 13.2.b.1). //! //! Vérifie que : //! 1. `wl_subcompositor` est annoncé comme global à v1 //! 2. Le bind du global réussit //! 3. `get_subsurface(child, parent)` instancie un `wl_subsurface` sans //! erreur protocole //! 4. `set_position`, `set_sync`, `set_desync` ne génèrent pas d'erreur //! 5. `destroy` sur le wl_subsurface ne génère pas d'erreur //! //! Logique serveur dupliquée verbatim de redox-wl-wayland-frontend/src/lib.rs //! (GlobalDispatch + Dispatch pour wl_subcompositor et wl_subsurface + //! minimum pour wl_compositor pour pouvoir créer 2 wl_surfaces). //! //! Si une erreur protocole est envoyée (bad_surface, etc.), wayland-client //! propage en error sur l'event_queue, et le test panique avec un message //! clair. use std::os::unix::net::UnixStream; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; use wayland_server::backend::{ClientData, ClientId, DisconnectReason}; use wayland_server::{ protocol::{ wl_compositor as srv_wl_compositor, wl_subcompositor as srv_wl_subcompositor, wl_subsurface as srv_wl_subsurface, wl_surface as srv_wl_surface, }, Client as SrvClient, DataInit, Display, DisplayHandle, GlobalDispatch, }; use wayland_client::{ backend::Backend, protocol::{wl_compositor, wl_registry, wl_subcompositor, wl_subsurface, wl_surface}, Connection, Dispatch as CliDispatch, QueueHandle, }; const COMPOSITOR_VERSION: u32 = 5; const SUBCOMPOSITOR_VERSION: u32 = 1; // ============= server-side: state minimal ============= struct ServerState; struct StubClientData; impl ClientData for StubClientData { fn initialized(&self, _: ClientId) {} fn disconnected(&self, _: ClientId, _: DisconnectReason) {} } // Données par-wl_subsurface, duplicate de notre prod code. #[allow(dead_code)] struct SubsurfaceData { parent: srv_wl_surface::WlSurface, child: srv_wl_surface::WlSurface, position: Mutex<(i32, i32)>, sync: AtomicBool, } // --- wl_compositor (minimum pour fabriquer des wl_surfaces) --- impl GlobalDispatch for ServerState { fn bind( _state: &mut Self, _handle: &DisplayHandle, _client: &SrvClient, resource: wayland_server::New, _data: &(), data_init: &mut DataInit<'_, Self>, ) { data_init.init(resource, ()); } } impl wayland_server::Dispatch for ServerState { fn request( _state: &mut Self, _client: &SrvClient, _r: &srv_wl_compositor::WlCompositor, request: srv_wl_compositor::Request, _data: &(), _dh: &DisplayHandle, data_init: &mut DataInit<'_, Self>, ) { if let srv_wl_compositor::Request::CreateSurface { id } = request { data_init.init(id, ()); } } } impl wayland_server::Dispatch for ServerState { fn request( _state: &mut Self, _client: &SrvClient, _r: &srv_wl_surface::WlSurface, _request: srv_wl_surface::Request, _data: &(), _dh: &DisplayHandle, _data_init: &mut DataInit<'_, Self>, ) { } } // --- wl_subcompositor (sous test) --- impl GlobalDispatch for ServerState { fn bind( _state: &mut Self, _handle: &DisplayHandle, _client: &SrvClient, resource: wayland_server::New, _data: &(), data_init: &mut DataInit<'_, Self>, ) { data_init.init(resource, ()); } } impl wayland_server::Dispatch for ServerState { fn request( _state: &mut Self, _client: &SrvClient, _r: &srv_wl_subcompositor::WlSubcompositor, request: srv_wl_subcompositor::Request, _data: &(), _dh: &DisplayHandle, data_init: &mut DataInit<'_, Self>, ) { match request { srv_wl_subcompositor::Request::Destroy => {} srv_wl_subcompositor::Request::GetSubsurface { id, surface, parent, } => { let data = Arc::new(SubsurfaceData { parent, child: surface, position: Mutex::new((0, 0)), sync: AtomicBool::new(true), }); data_init.init(id, data); } _ => {} } } } impl wayland_server::Dispatch> for ServerState { fn request( _state: &mut Self, _client: &SrvClient, _r: &srv_wl_subsurface::WlSubsurface, request: srv_wl_subsurface::Request, data: &Arc, _dh: &DisplayHandle, _data_init: &mut DataInit<'_, Self>, ) { match request { srv_wl_subsurface::Request::Destroy => {} srv_wl_subsurface::Request::SetPosition { x, y } => { *data.position.lock().unwrap() = (x, y); } srv_wl_subsurface::Request::PlaceAbove { sibling: _ } | srv_wl_subsurface::Request::PlaceBelow { sibling: _ } => {} srv_wl_subsurface::Request::SetSync => { data.sync.store(true, Ordering::Relaxed); } srv_wl_subsurface::Request::SetDesync => { data.sync.store(false, Ordering::Relaxed); } _ => {} } } } // ============= client-side ============= #[derive(Default)] struct ClientState { compositor_global: Option, subcompositor_global: Option, subcompositor_version: Option, last_error: Option, } impl CliDispatch for ClientState { fn event( state: &mut Self, _: &wl_registry::WlRegistry, event: wl_registry::Event, _: &(), _: &Connection, _: &QueueHandle, ) { if let wl_registry::Event::Global { name, interface, version, } = event { if interface == "wl_compositor" { state.compositor_global = Some(name); } else if interface == "wl_subcompositor" { state.subcompositor_global = Some(name); state.subcompositor_version = Some(version); } } } } impl CliDispatch for ClientState { fn event( _: &mut Self, _: &wl_compositor::WlCompositor, _: wl_compositor::Event, _: &(), _: &Connection, _: &QueueHandle, ) { } } impl CliDispatch for ClientState { fn event( _: &mut Self, _: &wl_surface::WlSurface, _: wl_surface::Event, _: &(), _: &Connection, _: &QueueHandle, ) { } } impl CliDispatch for ClientState { fn event( _: &mut Self, _: &wl_subcompositor::WlSubcompositor, _: wl_subcompositor::Event, _: &(), _: &Connection, _: &QueueHandle, ) { } } impl CliDispatch for ClientState { fn event( _: &mut Self, _: &wl_subsurface::WlSubsurface, _: wl_subsurface::Event, _: &(), _: &Connection, _: &QueueHandle, ) { } } // ============= test ============= #[test] fn wl_subcompositor_protocol_full_cycle() { let (s_stream, c_stream) = UnixStream::pair().expect("UnixStream::pair"); let server_thread = thread::spawn(move || { let mut display: Display = Display::new().unwrap(); let dh = display.handle(); dh.create_global::( COMPOSITOR_VERSION, (), ); dh.create_global::( SUBCOMPOSITOR_VERSION, (), ); s_stream.set_nonblocking(true).unwrap(); let _client = display .handle() .insert_client(s_stream, Arc::new(StubClientData)) .unwrap(); let mut state = ServerState; let start = std::time::Instant::now(); while start.elapsed() < Duration::from_secs(5) { let _ = display.dispatch_clients(&mut state); let _ = display.flush_clients(); thread::sleep(Duration::from_millis(10)); } }); c_stream.set_nonblocking(false).unwrap(); let backend = Backend::connect(c_stream).unwrap(); let conn = Connection::from_backend(backend); let mut event_queue = conn.new_event_queue::(); let qh = event_queue.handle(); let display = conn.display(); let _reg = display.get_registry(&qh, ()); let mut state = ClientState::default(); event_queue .roundtrip(&mut state) .expect("roundtrip registry"); // 1. Vérifs annonces globals let compositor_name = state.compositor_global.expect("wl_compositor non annoncé"); let subcomp_name = state .subcompositor_global .expect("wl_subcompositor non annoncé"); assert_eq!( state.subcompositor_version, Some(1), "wl_subcompositor doit être annoncé à v1, got {:?}", state.subcompositor_version ); // 2. Bind wl_compositor pour créer 2 surfaces let registry = display.get_registry(&qh, ()); let compositor = registry.bind::(compositor_name, 5, &qh, ()); let parent = compositor.create_surface(&qh, ()); let child = compositor.create_surface(&qh, ()); // 3. Bind wl_subcompositor + get_subsurface let subcomp = registry.bind::(subcomp_name, 1, &qh, ()); let subsurface = subcomp.get_subsurface(&child, &parent, &qh, ()); // 4. Exercer set_position / set_sync / set_desync subsurface.set_position(50, 50); subsurface.set_sync(); subsurface.set_desync(); subsurface.place_above(&parent); // valable même si parent n'est pas sibling — on no-op côté serveur subsurface.set_position(100, 100); // 5. Roundtrip pour driver tous les events et capter d'éventuelles erreurs for _ in 0..5 { event_queue .roundtrip(&mut state) .expect("roundtrip pendant exercise"); } // 6. Destroy subsurface.destroy(); event_queue .roundtrip(&mut state) .expect("roundtrip après destroy"); // 7. Cleanup drop(conn); server_thread.join().unwrap(); assert!( state.last_error.is_none(), "Erreur protocole détectée : {:?}", state.last_error ); }