Initial commit: phases 1-3 du portage Wayland Rust pour Redox OS

Plan directeur 14 phases / 5 ans (REDOX_COSMIC_XWAYLAND_RS_PLAN.md).

Phase 1 — Audit Redox (docs/existing-redox-gui.md, 486 lignes) :
- Orbital, graphics-ipc (API DRM compatible Linux subset KMS), inputd, vesad
- relibc support : AF_UNIX, SCM_RIGHTS, shm_open, mmap, poll
- 3 manques identifiés : memfd_create, keymap XKB, AT-SPI

Phase 2 — Validation primitives sur Redox via redoxer (5 tests + 1 POC) :
- test-unix-socket : SOCK_STREAM Wayland-shaped roundtrip
- test-fd-passing : SCM_RIGHTS mono-process (artefact kernel)
- test-fd-passing-fork : SCM_RIGHTS multi-process (validation Wayland critique)
- test-shm-open : shm_open + mmap + persistance + unlink
- test-poll-multifd : poll() multiplexing + POLLHUP
- poc-pixels : datapath shm + SCM_RIGHTS bout en bout (10000 pixels ARGB)

Phase 3 — wayland-rs sur Redox (compile + runtime) :
- wayland-{scanner,backend,server,client} compilent pour x86_64-unknown-redox
  sans patch upstream (rustix supporte Redox via libc backend)
- test-handshake : server/client wl_registry handshake roundtrip
- test-shm-pipeline : pipeline complet (ListeningSocket Unix réel + fd passing
  via wl_shm.create_pool + wl_shm_pool + wl_buffer + wl_surface + commit +
  serveur lit pixels via fd reçu, validation pixel-perfect)

Verdict phase 3 : wayland-rs upstream est viable sur Redox out-of-the-box,
le port "Wayland sur Redox" est désormais un problème de compositor à écrire,
pas de stack à porter.

Prérequis build : redoxer (pas cargo direct, car CMSG_NXTHDR/CMSG_DATA
ne sont pas linkés autrement vers librelibc.a).

Leyoda 2026 – GPLv3
This commit is contained in:
Votre Nom 2026-05-08 17:41:55 +02:00
commit 53e6626231
21 changed files with 3676 additions and 0 deletions

View file

@ -0,0 +1,310 @@
//! POC — preuve par les pixels.
//!
//! Démontre concrètement que le datapath Wayland fonctionne sur Redox :
//! - un CLIENT crée une zone shm, dessine un pattern ARGB déterministe
//! - il envoie le fd au SERVEUR via Unix socket + SCM_RIGHTS
//! - le SERVEUR mappe la zone, vérifie chaque pixel, écrit un PPM
//!
//! C'est exactement ce que fait wl_shm + wl_buffer entre un client Wayland
//! et son compositor, sans le protocole Wayland lui-même. Si ce POC passe,
//! la phase 3 (port wayland-rs) peut démarrer en confiance.
//!
//! Mono-binaire qui fork() : le child fait le client, le parent le serveur.
use std::ffi::CString;
use std::io::{self, Write};
use std::mem::{self, MaybeUninit};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::process::ExitCode;
use std::ptr;
const W: usize = 200;
const H: usize = 200;
const PIXEL_BYTES: usize = 4;
const SHM_BYTES: usize = W * H * PIXEL_BYTES;
const SHM_NAME: &str = "/redox-wl-poc-pixels";
const PPM_OUTPUT: &str = "poc-shm-pixels-readback.ppm";
/// Pattern ARGB8888 déterministe : channel A = 0xFF, R = x, G = y, B = (x+y) mod 256.
fn expected_pixel(x: usize, y: usize) -> u32 {
let a: u32 = 0xFF;
let r: u32 = (x as u32) & 0xFF;
let g: u32 = (y as u32) & 0xFF;
let b: u32 = ((x + y) as u32) & 0xFF;
(a << 24) | (r << 16) | (g << 8) | b
}
fn errno_str() -> String {
let e = io::Error::last_os_error();
format!("{e} (errno={})", e.raw_os_error().unwrap_or(0))
}
unsafe fn make_socketpair() -> Result<(OwnedFd, OwnedFd), String> {
let mut fds: [RawFd; 2] = [-1, -1];
if libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, fds.as_mut_ptr()) != 0 {
return Err(format!("socketpair: {}", errno_str()));
}
Ok((OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])))
}
unsafe fn send_fd_over(socket: RawFd, fd_to_send: RawFd) -> Result<(), String> {
let mut iov_buf = [b'P'; 1];
let mut iov = libc::iovec {
iov_base: iov_buf.as_mut_ptr() as *mut _,
iov_len: 1,
};
let cmsg_space = libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![0u8; cmsg_space];
let mut msg: libc::msghdr = mem::zeroed();
msg.msg_iov = &mut iov as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;
let cmsg = libc::CMSG_FIRSTHDR(&msg);
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
(*cmsg).cmsg_len = libc::CMSG_LEN(mem::size_of::<RawFd>() as u32) as _;
let data_ptr = libc::CMSG_DATA(cmsg) as *mut RawFd;
ptr::write_unaligned(data_ptr, fd_to_send);
let n = libc::sendmsg(socket, &msg, 0);
if n < 0 {
return Err(format!("sendmsg: {}", errno_str()));
}
Ok(())
}
unsafe fn recv_fd_from(socket: RawFd) -> Result<OwnedFd, String> {
let mut iov_buf = [0u8; 1];
let mut iov = libc::iovec {
iov_base: iov_buf.as_mut_ptr() as *mut _,
iov_len: 1,
};
let cmsg_space = libc::CMSG_SPACE(mem::size_of::<RawFd>() as u32) as usize;
let mut cmsg_buf = vec![MaybeUninit::<u8>::uninit(); cmsg_space];
let mut msg: libc::msghdr = mem::zeroed();
msg.msg_iov = &mut iov as *mut _;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf.as_mut_ptr() as *mut _;
msg.msg_controllen = cmsg_space as _;
let n = libc::recvmsg(socket, &mut msg, 0);
if n < 0 {
return Err(format!("recvmsg: {}", errno_str()));
}
let cmsg = libc::CMSG_FIRSTHDR(&msg);
if cmsg.is_null() {
return Err("no cmsg received".into());
}
if (*cmsg).cmsg_level != libc::SOL_SOCKET || (*cmsg).cmsg_type != libc::SCM_RIGHTS {
return Err("unexpected cmsg type".into());
}
let data_ptr = libc::CMSG_DATA(cmsg) as *const RawFd;
let received_fd = ptr::read_unaligned(data_ptr);
Ok(OwnedFd::from_raw_fd(received_fd))
}
unsafe fn shm_create_and_map(name: &str, size: usize) -> Result<(OwnedFd, *mut u8), String> {
let cname = CString::new(name).unwrap();
let fd = libc::shm_open(cname.as_ptr(), libc::O_RDWR | libc::O_CREAT, 0o600);
if fd < 0 {
return Err(format!("shm_open: {}", errno_str()));
}
if libc::ftruncate(fd, size as _) != 0 {
libc::close(fd);
return Err(format!("ftruncate: {}", errno_str()));
}
let p = libc::mmap(
ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_SHARED,
fd,
0,
);
if p == libc::MAP_FAILED {
libc::close(fd);
return Err(format!("mmap (create): {}", errno_str()));
}
Ok((OwnedFd::from_raw_fd(fd), p as *mut u8))
}
unsafe fn mmap_existing(fd: RawFd, size: usize) -> Result<*mut u8, String> {
let p = libc::mmap(
ptr::null_mut(),
size,
libc::PROT_READ,
libc::MAP_SHARED,
fd,
0,
);
if p == libc::MAP_FAILED {
return Err(format!("mmap (recv): {}", errno_str()));
}
Ok(p as *mut u8)
}
unsafe fn child_main(child_sock: OwnedFd) -> ! {
eprintln!("[poc CLIENT pid={}] start", libc::getpid());
// Crée la zone shm
let (shm_fd, map) = match shm_create_and_map(SHM_NAME, SHM_BYTES) {
Ok(v) => v,
Err(e) => {
eprintln!("[poc CLIENT] FAIL shm_create_and_map: {e}");
libc::_exit(2);
}
};
eprintln!(
"[poc CLIENT] shm '{}' size={} mapped at {:p} fd={}",
SHM_NAME, SHM_BYTES, map, shm_fd.as_raw_fd()
);
// Dessine le pattern ARGB déterministe
let pixels = std::slice::from_raw_parts_mut(map as *mut u32, W * H);
for y in 0..H {
for x in 0..W {
pixels[y * W + x] = expected_pixel(x, y);
}
}
eprintln!(
"[poc CLIENT] painted {}x{} ARGB pattern (a=0xFF, r=x, g=y, b=(x+y)&0xFF)",
W, H
);
// Envoie le fd au serveur
if let Err(e) = send_fd_over(child_sock.as_raw_fd(), shm_fd.as_raw_fd()) {
eprintln!("[poc CLIENT] FAIL sendmsg: {e}");
libc::_exit(3);
}
eprintln!("[poc CLIENT] sent shm fd via SCM_RIGHTS");
// On peut munmap + close côté client : le serveur a son propre ref via SCM_RIGHTS
libc::munmap(map as *mut _, SHM_BYTES);
drop(shm_fd);
eprintln!("[poc CLIENT] done, exit 0");
libc::_exit(0);
}
unsafe fn parent_main(parent_sock: OwnedFd, child_pid: libc::pid_t) -> Result<(), String> {
eprintln!("[poc SERVER pid={}] waiting for fd from child {}", libc::getpid(), child_pid);
let received = recv_fd_from(parent_sock.as_raw_fd())?;
let recv_raw = received.as_raw_fd();
eprintln!("[poc SERVER] received fd={recv_raw}");
let map = mmap_existing(recv_raw, SHM_BYTES)?;
eprintln!("[poc SERVER] mmap'd received fd at {:p}", map);
// Vérifie chaque pixel
let pixels = std::slice::from_raw_parts(map as *const u32, W * H);
let mut mismatches: Vec<(usize, usize, u32, u32)> = Vec::new();
for y in 0..H {
for x in 0..W {
let got = pixels[y * W + x];
let want = expected_pixel(x, y);
if got != want {
if mismatches.len() < 5 {
mismatches.push((x, y, got, want));
}
}
}
}
if !mismatches.is_empty() {
for (x, y, got, want) in &mismatches {
eprintln!(
"[poc SERVER] MISMATCH at ({x},{y}): got {got:#010x} want {want:#010x}"
);
}
return Err(format!("{} pixel mismatches over {}", mismatches.len(), W * H));
}
eprintln!(
"[poc SERVER] all {} pixels match expected pattern",
W * H
);
// Bonus : dump en PPM pour visualisation (P6 binaire RGB)
match write_ppm(PPM_OUTPUT, pixels, W, H) {
Ok(()) => eprintln!("[poc SERVER] PPM dump written to {PPM_OUTPUT}"),
Err(e) => eprintln!("[poc SERVER] WARN: PPM dump failed: {e}"),
}
libc::munmap(map as *mut _, SHM_BYTES);
drop(received);
// Reap child
let mut status: libc::c_int = 0;
let r = libc::waitpid(child_pid, &mut status, 0);
if r < 0 {
return Err(format!("waitpid: {}", errno_str()));
}
if !libc::WIFEXITED(status) || libc::WEXITSTATUS(status) != 0 {
return Err(format!(
"child exited abnormally: status={status}"
));
}
eprintln!("[poc SERVER] child reaped cleanly");
// Cleanup shm
let cname = CString::new(SHM_NAME).unwrap();
libc::shm_unlink(cname.as_ptr());
Ok(())
}
fn write_ppm(path: &str, pixels: &[u32], w: usize, h: usize) -> io::Result<()> {
let mut f = std::fs::File::create(path)?;
write!(f, "P6\n{w} {h}\n255\n")?;
let mut row = vec![0u8; w * 3];
for y in 0..h {
for x in 0..w {
let argb = pixels[y * w + x];
let r = ((argb >> 16) & 0xFF) as u8;
let g = ((argb >> 8) & 0xFF) as u8;
let b = (argb & 0xFF) as u8;
row[x * 3] = r;
row[x * 3 + 1] = g;
row[x * 3 + 2] = b;
}
f.write_all(&row)?;
}
Ok(())
}
fn run() -> Result<(), String> {
println!(
"[poc] preuve par les pixels: client {}x{} ARGB → SCM_RIGHTS → serveur",
W, H
);
unsafe {
let (sock_parent, sock_child) = make_socketpair()?;
let pid = libc::fork();
if pid < 0 {
return Err(format!("fork: {}", errno_str()));
}
if pid == 0 {
drop(sock_parent);
child_main(sock_child);
}
drop(sock_child);
parent_main(sock_parent, pid)?;
}
Ok(())
}
fn main() -> ExitCode {
match run() {
Ok(()) => {
println!("[poc] PASS: full Wayland-like datapath validated on Redox");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("[poc] FAIL: {e}");
ExitCode::FAILURE
}
}
}