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:
commit
53e6626231
21 changed files with 3676 additions and 0 deletions
310
crates/redox-wl-poc-pixels/src/main.rs
Normal file
310
crates/redox-wl-poc-pixels/src/main.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue