redox-wayland-compositor/crates/redox-wl-test-wl-output/tests/subcompositor_native.rs
Votre Nom f9c3de13da Phase 13.2.b.1 — wl_subcompositor protocole (sans rendering)
Implémentation minimale du global wl_subcompositor v1 côté compositor.
Couvre uniquement le protocole : bind, GetSubsurface, et tous les
requests wl_subsurface (SetPosition, PlaceAbove/Below, SetSync/SetDesync,
Destroy). Les données sont stockées dans SubsurfaceData mais ne sont
pas encore consommées par le rendering — c'est le scope de 13.2.b.2.

Ce qui marche maintenant :
- Un client qui bind wl_subcompositor le trouve à v1
- get_subsurface(child, parent) ne crashe pas, retourne un wl_subsurface
  valide avec SubsurfaceData attaché (parent ref, child ref, position
  pending, sync mode default true)
- Toutes les requests subséquentes sont acceptées sans erreur protocole
- destroy : no-op propre (la resource est nettoyée par wayland-server)

Limitations explicites pour 13.2.b.2 :
- Pas de role-tracking (la spec exige bad_surface si la wl_surface
  enfant a déjà un rôle ; on log debug seulement)
- Pas de cascade sync : un commit du parent ne propage pas les states
  pending des subsurfaces
- PlaceAbove/Below : no-op (single-subsurface use-case suffit pour 13.2.b)
- compose_into ne sait pas dessiner les subsurfaces

Test natif (cargo test, sans QEMU) :
- Vérifie l'annonce du global à v1
- Bind + create_surface ×2 + get_subsurface + tous les requests
  wl_subsurface successifs + destroy
- Roundtrip à chaque étape pour capter d'éventuelles erreurs protocole
- PASS le 2026-05-16 sur CachyOS

Leyoda 2026 – GPLv3
2026-05-16 13:01:45 +02:00

364 lines
11 KiB
Rust

//! 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<srv_wl_compositor::WlCompositor, ()> for ServerState {
fn bind(
_state: &mut Self,
_handle: &DisplayHandle,
_client: &SrvClient,
resource: wayland_server::New<srv_wl_compositor::WlCompositor>,
_data: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl wayland_server::Dispatch<srv_wl_compositor::WlCompositor, ()> 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<srv_wl_surface::WlSurface, ()> 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<srv_wl_subcompositor::WlSubcompositor, ()> for ServerState {
fn bind(
_state: &mut Self,
_handle: &DisplayHandle,
_client: &SrvClient,
resource: wayland_server::New<srv_wl_subcompositor::WlSubcompositor>,
_data: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl wayland_server::Dispatch<srv_wl_subcompositor::WlSubcompositor, ()> 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<srv_wl_subsurface::WlSubsurface, Arc<SubsurfaceData>>
for ServerState
{
fn request(
_state: &mut Self,
_client: &SrvClient,
_r: &srv_wl_subsurface::WlSubsurface,
request: srv_wl_subsurface::Request,
data: &Arc<SubsurfaceData>,
_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<u32>,
subcompositor_global: Option<u32>,
subcompositor_version: Option<u32>,
last_error: Option<String>,
}
impl CliDispatch<wl_registry::WlRegistry, ()> for ClientState {
fn event(
state: &mut Self,
_: &wl_registry::WlRegistry,
event: wl_registry::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
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<wl_compositor::WlCompositor, ()> for ClientState {
fn event(
_: &mut Self,
_: &wl_compositor::WlCompositor,
_: wl_compositor::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl CliDispatch<wl_surface::WlSurface, ()> for ClientState {
fn event(
_: &mut Self,
_: &wl_surface::WlSurface,
_: wl_surface::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl CliDispatch<wl_subcompositor::WlSubcompositor, ()> for ClientState {
fn event(
_: &mut Self,
_: &wl_subcompositor::WlSubcompositor,
_: wl_subcompositor::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
impl CliDispatch<wl_subsurface::WlSubsurface, ()> for ClientState {
fn event(
_: &mut Self,
_: &wl_subsurface::WlSubsurface,
_: wl_subsurface::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}
// ============= 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<ServerState> = Display::new().unwrap();
let dh = display.handle();
dh.create_global::<ServerState, srv_wl_compositor::WlCompositor, _>(
COMPOSITOR_VERSION,
(),
);
dh.create_global::<ServerState, srv_wl_subcompositor::WlSubcompositor, _>(
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::<ClientState>();
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::<wl_compositor::WlCompositor, _, _>(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::<wl_subcompositor::WlSubcompositor, _, _>(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
);
}