//! Test natif (CachyOS, hors QEMU) : version gating de wl_output. //! //! On simule en-process l'init du global wl_output v3 avec la MÊME logique //! de gating que `redox-wl-wayland-frontend::lib.rs` : //! - geometry, mode : toujours envoyés //! - scale : seulement si version >= 3 //! - done : seulement si version >= 2 //! //! Un seul process : serveur sur un thread + client sur le thread principal, //! reliés par `UnixStream::pair()`. Pas besoin de Redox, pas besoin de QEMU. use std::os::unix::net::UnixStream; use std::sync::Arc; use std::thread; use std::time::Duration; use wayland_server::backend::{ClientData, ClientId, DisconnectReason}; use wayland_server::{ protocol::wl_output as srv_wl_output, Client as SrvClient, DataInit, Display, DisplayHandle, GlobalDispatch, Resource, }; use wayland_client::{ backend::Backend, protocol::{wl_output as cli_wl_output, wl_registry}, Connection, Dispatch as CliDispatch, QueueHandle, }; const OUTPUT_VERSION: u32 = 3; const SCREEN_W: i32 = 1280; const SCREEN_H: i32 = 800; // ============= server side ============= struct ServerState; struct StubClientData; impl ClientData for StubClientData { fn initialized(&self, _: ClientId) {} fn disconnected(&self, _: ClientId, _: DisconnectReason) {} } impl GlobalDispatch for ServerState { fn bind( _state: &mut Self, _handle: &DisplayHandle, _client: &SrvClient, resource: wayland_server::New, _data: &(), data_init: &mut DataInit<'_, Self>, ) { let output = data_init.init(resource, ()); let version = output.version(); // Logique identique à redox-wl-wayland-frontend/src/lib.rs output.geometry( 0, 0, 0, 0, srv_wl_output::Subpixel::Unknown, "Redox".to_string(), "redox-wl-output:0".to_string(), srv_wl_output::Transform::Normal, ); let mode_flags = srv_wl_output::Mode::Current | srv_wl_output::Mode::Preferred; output.mode(mode_flags, SCREEN_W, SCREEN_H, 60_000); if version >= 3 { output.scale(1); } if version >= 2 { output.done(); } } } impl wayland_server::Dispatch for ServerState { fn request( _state: &mut Self, _client: &SrvClient, _r: &srv_wl_output::WlOutput, _request: srv_wl_output::Request, _data: &(), _dh: &DisplayHandle, _data_init: &mut DataInit<'_, Self>, ) { } } // ============= client side ============= #[derive(Default, Debug, Clone, Copy)] struct OutputObs { seen_geometry: bool, seen_mode: bool, seen_scale: bool, seen_done: bool, mode_flags: u32, scale_factor: i32, } #[derive(Default)] struct ClientState { global_name: Option, obs: [OutputObs; 3], cur: 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, .. } = event { if interface == "wl_output" { state.global_name = Some(name); } } } } impl CliDispatch for ClientState { fn event( state: &mut Self, _: &cli_wl_output::WlOutput, event: cli_wl_output::Event, _: &(), _: &Connection, _: &QueueHandle, ) { let Some(idx) = state.cur else { return }; let obs = &mut state.obs[idx]; match event { cli_wl_output::Event::Geometry { .. } => obs.seen_geometry = true, cli_wl_output::Event::Mode { flags, .. } => { obs.seen_mode = true; obs.mode_flags = flags.into(); } cli_wl_output::Event::Scale { factor } => { obs.seen_scale = true; obs.scale_factor = factor; } cli_wl_output::Event::Done => obs.seen_done = true, _ => {} } } } // ============= test ============= #[test] fn wl_output_v123_gating() { // 1. Setup serveur dans un thread 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::(OUTPUT_VERSION, ()); s_stream.set_nonblocking(true).unwrap(); let _client = display .handle() .insert_client(s_stream, Arc::new(StubClientData)) .unwrap(); let mut state = ServerState; // Dispatch loop : on tourne ~5 secondes pour absorber tous les // round-trips du test client. Le test signale la fin en fermant // le socket côté client, ce qui sort dispatch_clients sur error. 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)); } }); // 2. Client connecte via l'autre bout du socket 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 _reg1 = display.get_registry(&qh, ()); let mut state = ClientState::default(); event_queue.roundtrip(&mut state).expect("roundtrip registry"); let global_name = state.global_name.expect("wl_output non advertised"); // 3. Bind aux 3 versions let registry = display.get_registry(&qh, ()); for (idx, version) in [(0usize, 1u32), (1, 2), (2, 3)].iter() { state.cur = Some(*idx); let _o = registry.bind::(global_name, *version, &qh, ()); for _ in 0..3 { event_queue.roundtrip(&mut state).expect("roundtrip bind"); } state.cur = None; } // 4. Drop client to make server exit drop(conn); server_thread.join().unwrap(); // 5. Asserts let v1 = &state.obs[0]; assert!(v1.seen_geometry, "v1: geometry attendu"); assert!(v1.seen_mode, "v1: mode attendu"); assert!(!v1.seen_scale, "v1: scale ne devrait PAS être envoyé"); assert!(!v1.seen_done, "v1: done ne devrait PAS être envoyé"); let v2 = &state.obs[1]; assert!(v2.seen_geometry, "v2: geometry attendu"); assert!(v2.seen_mode, "v2: mode attendu"); assert!( !v2.seen_scale, "v2: scale ne devrait PAS être envoyé (gating conservateur v3+)" ); assert!(v2.seen_done, "v2: done attendu"); let v3 = &state.obs[2]; assert!(v3.seen_geometry, "v3: geometry attendu"); assert!(v3.seen_mode, "v3: mode attendu"); assert!(v3.seen_scale, "v3: scale attendu"); assert!(v3.seen_done, "v3: done attendu"); assert_eq!(v3.scale_factor, 1, "v3: scale_factor doit être 1"); assert!( v3.mode_flags & 0x1 != 0, "v3: mode flags doit contenir CURRENT (0x1)" ); assert!( v3.mode_flags & 0x2 != 0, "v3: mode flags doit contenir PREFERRED (0x2)" ); }