🎉 Phase 7.1 — xdg-shell minimal validé runtime
Capture preuve : docs/phase7-1-xdg-toplevel.png — fenêtre 320x240 du
client externe positionnée à (60, 60) par le compositor (cascade par
défaut), avec bandes verticales arc-en-ciel + bordure noire 2px.
Différence visuelle vs phase 6.4 : la fenêtre est maintenant
positionnée par le compositor (pas plaquée à 0,0).
Modifications redox-wl-wayland-frontend :
- + dep wayland-protocols (feature server)
- xdg_wm_base global v5
- XdgSurfaceData { wl_surface, last_serial, acked_serial }
- XdgToplevelData { title, app_id }
- SurfaceData.xdg_pending_initial_configure : bloque l'affichage tant
que ack-configure pas reçu (sémantique xdg-shell standard)
- WaylandFrontend.next_xdg_serial : counter monotone serial
- WaylandFrontend.next_toplevel_index : cascade position +60 par fenêtre
- DEFAULT_TOPLEVEL_SIZE = 640x480 envoyé en initial configure
- Dispatch xdg_wm_base : GetXdgSurface, CreatePositioner (no-op),
Pong (no-op), Destroy (no-op)
- Dispatch xdg_positioner + xdg_popup : no-op (out of scope)
- Dispatch xdg_surface :
- GetToplevel → cascade pos + envoie xdg_toplevel.configure(640,480)
+ xdg_surface.configure(serial)
- AckConfigure(serial) → enregistre + débloque affichage
- Destroy → cache la surface
- Dispatch xdg_toplevel :
- SetTitle / SetAppId → stocke
- tout le reste ignoré (move, resize, maximized, ...)
redox-wl-test-client-shm adapté :
- + dep wayland-protocols (feature client)
- bind 3 globals : wl_compositor, wl_shm, xdg_wm_base
- Dispatch XdgWmBase::Event::Ping → pong (au cas où)
- Dispatch XdgSurface::Event::Configure → store serial
- Dispatch XdgToplevel::Event::Configure / Close → log
- Workflow runtime :
1. create_surface + get_xdg_surface + get_toplevel
2. set_title / set_app_id
3. surface.commit (initial empty)
4. attente Configure event (timeout 5s)
5. ack_configure(serial)
6. shm + buffer + attach + commit POST-ack
7. boucle 25s
8. destroy propre
Init submodule wayland-rs/wayland-protocols/protocols (XML files
gitlab.freedesktop.org/wayland/wayland-protocols).
Validation runtime QEMU complète :
[client] globals : compositor=true shm=true xdg_wm_base=true
[client] xdg_toplevel configure suggéré : 640x480
[client] initial configure reçu, serial=1
[client] ack_configure(1)
[client] buffer attach + damage + commit POST-ack
[comp] tick=30..450 surfaces=1 elapsed=1.2..18.2s
PNG capturée à T+14s : surface visible à (60,60), bandes RGB+jaune+
violet+orange comme attendu.
Critère de fin 7.1 validé : client xdg-shell crée toplevel, reçoit
configure, ack, commit, affiche via shm, sans panic serveur, 18s
stable, destroy propre.
Limitations connues : focus, cursor, move/resize, multi-client,
robustesse — sous-tickets 7.2-7.7.
Image Redox restaurée à boot Orbital normal.
docs/phase7-1-xdg-shell.md : compte-rendu complet, cycle de vie,
limitations, plan 7.2.
Leyoda 2026 – GPLv3
This commit is contained in:
parent
8a897d975d
commit
4bff319c7f
6 changed files with 600 additions and 36 deletions
|
|
@ -1,13 +1,11 @@
|
|||
//! Phase 6.4 — Client Wayland test.
|
||||
//! Phase 7.1 — Client Wayland xdg-shell.
|
||||
//!
|
||||
//! Se connecte au socket Wayland exposé par le compositor à
|
||||
//! `/tmp/redox-wl-comp.sock`, crée une surface 320x240, peint un
|
||||
//! pattern ARGB déterministe (dégradé orangé), commit. Reste connecté
|
||||
//! 30s pour qu'on puisse capturer l'écran.
|
||||
//! Se connecte à `/tmp/redox-wl-comp.sock`, bind les globals
|
||||
//! wl_compositor + wl_shm + xdg_wm_base, crée une fenêtre xdg_toplevel,
|
||||
//! attend l'initial configure, ack, peint un buffer ARGB et commit.
|
||||
//!
|
||||
//! C'est le test critique de phase 6.4 : si le compositor affiche
|
||||
//! ce qui suit dans la frame QEMU, on a un VRAI compositor Wayland
|
||||
//! qui rend des pixels venant d'un client externe.
|
||||
//! Vs phase 6.4 : on passe par xdg-shell (placement managé par le
|
||||
//! compositor) au lieu de wl_surface seul.
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::fs::OpenOptions;
|
||||
|
|
@ -28,6 +26,11 @@ use wayland_client::{
|
|||
wl_shm_pool::WlShmPool, wl_surface::WlSurface,
|
||||
},
|
||||
};
|
||||
use wayland_protocols::xdg::shell::client::{
|
||||
xdg_surface::{self, XdgSurface},
|
||||
xdg_toplevel::{self, XdgToplevel},
|
||||
xdg_wm_base::{self, XdgWmBase},
|
||||
};
|
||||
|
||||
const SOCKET_PATH: &str = "/tmp/redox-wl-comp.sock";
|
||||
const W: i32 = 320;
|
||||
|
|
@ -60,6 +63,10 @@ fn dlog(s: &str) {
|
|||
struct ClientState {
|
||||
compositor: Option<WlCompositor>,
|
||||
shm: Option<WlShm>,
|
||||
wm_base: Option<XdgWmBase>,
|
||||
/// Le serial du dernier configure reçu, à ack.
|
||||
pending_serial: Option<u32>,
|
||||
configured: bool,
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, ()> for ClientState {
|
||||
|
|
@ -79,6 +86,9 @@ impl Dispatch<wl_registry::WlRegistry, ()> for ClientState {
|
|||
"wl_shm" => {
|
||||
state.shm = Some(registry.bind(name, version.min(1), qh, ()));
|
||||
}
|
||||
"xdg_wm_base" => {
|
||||
state.wm_base = Some(registry.bind(name, version.min(5), qh, ()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -106,6 +116,63 @@ noop!(WlShmPool);
|
|||
noop!(WlBuffer);
|
||||
noop!(WlSurface);
|
||||
|
||||
// xdg_wm_base : doit répondre aux pings (mais on n'envoie pas) ; on ignore
|
||||
impl Dispatch<XdgWmBase, ()> for ClientState {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
wm_base: &XdgWmBase,
|
||||
event: xdg_wm_base::Event,
|
||||
_: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let xdg_wm_base::Event::Ping { serial } = event {
|
||||
wm_base.pong(serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// xdg_surface : reçoit les configure, on stocke le serial pour ack
|
||||
impl Dispatch<XdgSurface, ()> for ClientState {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_xdg_surf: &XdgSurface,
|
||||
event: xdg_surface::Event,
|
||||
_: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let xdg_surface::Event::Configure { serial } = event {
|
||||
state.pending_serial = Some(serial);
|
||||
state.configured = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// xdg_toplevel : configure (size + states) + close
|
||||
impl Dispatch<XdgToplevel, ()> for ClientState {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_r: &XdgToplevel,
|
||||
event: xdg_toplevel::Event,
|
||||
_: &(),
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
xdg_toplevel::Event::Configure { width, height, .. } => {
|
||||
dlog(&format!(
|
||||
"[client] xdg_toplevel configure suggéré : {width}x{height}"
|
||||
));
|
||||
}
|
||||
xdg_toplevel::Event::Close => {
|
||||
dlog("[client] xdg_toplevel close request");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn create_shm_with_pattern(name: &str) -> Result<OwnedFd, String> {
|
||||
let cname = CString::new(name).unwrap();
|
||||
let _ = libc::shm_unlink(cname.as_ptr());
|
||||
|
|
@ -133,14 +200,23 @@ unsafe fn create_shm_with_pattern(name: &str) -> Result<OwnedFd, String> {
|
|||
return Err("mmap failed".into());
|
||||
}
|
||||
|
||||
// Pattern ARGB : dégradé orangé + bandes diagonales pour reconnaître
|
||||
// Pattern ARGB : bandes verticales colorées + numéro de fenêtre simulé
|
||||
let pixels = std::slice::from_raw_parts_mut(p as *mut u32, (W * H) as usize);
|
||||
for y in 0..H {
|
||||
for x in 0..W {
|
||||
let r: u32 = (200 + (x * 55 / W)) as u32 & 0xFF;
|
||||
let g: u32 = (80 + (y * 100 / H)) as u32 & 0xFF;
|
||||
let b: u32 = (((x + y) & 0xFF) as u32).saturating_sub(100);
|
||||
pixels[(y * W + x) as usize] = (0xFF << 24) | (r << 16) | (g << 8) | b;
|
||||
let band = (x / 32) % 6;
|
||||
let color: u32 = match band {
|
||||
0 => 0xFF_F2_55_44,
|
||||
1 => 0xFF_F0_8E_3A,
|
||||
2 => 0xFF_E8_C5_3A,
|
||||
3 => 0xFF_5A_C0_50,
|
||||
4 => 0xFF_45_8B_E0,
|
||||
5 => 0xFF_8C_60_D0,
|
||||
_ => 0xFF_FF_FF_FF,
|
||||
};
|
||||
// Bordure noire 2px
|
||||
let on_border = x < 2 || x >= W - 2 || y < 2 || y >= H - 2;
|
||||
pixels[(y * W + x) as usize] = if on_border { 0xFF_10_10_10 } else { color };
|
||||
}
|
||||
}
|
||||
libc::munmap(p, SIZE as usize);
|
||||
|
|
@ -150,7 +226,7 @@ unsafe fn create_shm_with_pattern(name: &str) -> Result<OwnedFd, String> {
|
|||
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
dlog("[client] connect to compositor");
|
||||
|
||||
// Attendre que le socket existe (compositor démarré)
|
||||
// Attente du socket
|
||||
for i in 0..50 {
|
||||
if std::path::Path::new(SOCKET_PATH).exists() {
|
||||
break;
|
||||
|
|
@ -171,36 +247,64 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let mut state = ClientState::default();
|
||||
event_queue.roundtrip(&mut state)?;
|
||||
dlog(&format!(
|
||||
"[client] globals : compositor={} shm={}",
|
||||
"[client] globals : compositor={} shm={} xdg_wm_base={}",
|
||||
state.compositor.is_some(),
|
||||
state.shm.is_some()
|
||||
state.shm.is_some(),
|
||||
state.wm_base.is_some()
|
||||
));
|
||||
|
||||
let compositor = state
|
||||
.compositor
|
||||
.clone()
|
||||
.ok_or("no wl_compositor global")?;
|
||||
let compositor = state.compositor.clone().ok_or("no wl_compositor global")?;
|
||||
let shm = state.shm.clone().ok_or("no wl_shm global")?;
|
||||
let wm_base = state.wm_base.clone().ok_or("no xdg_wm_base global")?;
|
||||
|
||||
// Crée shm + pattern
|
||||
let fd = unsafe { create_shm_with_pattern("/redox-wl-client-shm") }?;
|
||||
dlog(&format!("[client] shm créé, peint {}x{} ARGB", W, H));
|
||||
|
||||
// wl_shm_pool + wl_buffer
|
||||
let pool = shm.create_pool(fd.as_fd(), SIZE, &qh, ());
|
||||
let buffer = pool.create_buffer(0, W, H, STRIDE, wayland_client::protocol::wl_shm::Format::Argb8888, &qh, ());
|
||||
|
||||
// wl_surface
|
||||
// 1. Création wl_surface + xdg_surface + xdg_toplevel
|
||||
let surface = compositor.create_surface(&qh, ());
|
||||
let xdg_surface = wm_base.get_xdg_surface(&surface, &qh, ());
|
||||
let toplevel = xdg_surface.get_toplevel(&qh, ());
|
||||
toplevel.set_title("Phase 7.1 client".to_string());
|
||||
toplevel.set_app_id("redox.wl.test.client.shm".to_string());
|
||||
surface.commit(); // commit initial pour signaler "prêt à recevoir configure"
|
||||
dlog("[client] xdg_toplevel créé, attente initial configure");
|
||||
|
||||
// 2. Attendre l'initial configure
|
||||
let start = std::time::Instant::now();
|
||||
while !state.configured && start.elapsed() < Duration::from_secs(5) {
|
||||
event_queue.roundtrip(&mut state)?;
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
if !state.configured {
|
||||
return Err("no initial configure received".into());
|
||||
}
|
||||
let serial = state.pending_serial.unwrap_or(0);
|
||||
dlog(&format!("[client] initial configure reçu, serial={serial}"));
|
||||
|
||||
// 3. Ack le configure
|
||||
xdg_surface.ack_configure(serial);
|
||||
state.pending_serial = None;
|
||||
dlog(&format!("[client] ack_configure({serial})"));
|
||||
|
||||
// 4. Préparer + attacher le buffer
|
||||
let fd = unsafe { create_shm_with_pattern("/redox-wl-client-shm-71") }?;
|
||||
let pool = shm.create_pool(fd.as_fd(), SIZE, &qh, ());
|
||||
let buffer = pool.create_buffer(
|
||||
0,
|
||||
W,
|
||||
H,
|
||||
STRIDE,
|
||||
wayland_client::protocol::wl_shm::Format::Argb8888,
|
||||
&qh,
|
||||
(),
|
||||
);
|
||||
|
||||
surface.attach(Some(&buffer), 0, 0);
|
||||
surface.damage_buffer(0, 0, W, H);
|
||||
surface.commit();
|
||||
dlog("[client] surface attach + damage + commit envoyés");
|
||||
dlog("[client] buffer attach + damage + commit POST-ack");
|
||||
|
||||
event_queue.flush()?;
|
||||
let _ = event_queue.roundtrip(&mut state);
|
||||
|
||||
// Reste connecté ~25s pour qu'on puisse capturer
|
||||
// 5. Boucle vivante 25 secondes
|
||||
let start = std::time::Instant::now();
|
||||
while start.elapsed() < Duration::from_secs(25) {
|
||||
let _ = event_queue.dispatch_pending(&mut state);
|
||||
|
|
@ -208,10 +312,11 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
|
||||
dlog("[client] done, exit propre");
|
||||
let _ = surface; // tag explicite pour rappeler que la surface vit jusqu'à la fin
|
||||
let _ = buffer;
|
||||
let _ = pool;
|
||||
dlog("[client] done, destroy propre");
|
||||
toplevel.destroy();
|
||||
xdg_surface.destroy();
|
||||
surface.destroy();
|
||||
let _ = event_queue.flush();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue