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
589
crates/redox-wl-test-shm-pipeline/src/main.rs
Normal file
589
crates/redox-wl-test-shm-pipeline/src/main.rs
Normal file
|
|
@ -0,0 +1,589 @@
|
|||
//! Test 6 — Pipeline complet wayland-rs sur Redox.
|
||||
//!
|
||||
//! C'est ce que fait un compositor Wayland minimal :
|
||||
//! - serveur expose un socket Unix (ListeningSocket)
|
||||
//! - client se connecte via WAYLAND_SOCKET (vrai pattern Wayland)
|
||||
//! - client crée wl_shm_pool depuis un fd shm
|
||||
//! - client crée wl_buffer depuis le pool, peint un pattern ARGB
|
||||
//! - client crée wl_surface, attach, damage, commit
|
||||
//! - serveur reçoit le commit, mappe le buffer fd, lit les pixels, vérifie
|
||||
//!
|
||||
//! Si ce test passe, le pipeline shm Wayland complet fonctionne sur Redox.
|
||||
//! C'est la preuve technique que la phase 4-7 du plan directeur (display
|
||||
//! backend + input + surfaces shm + compositor minimal) est réalisable
|
||||
//! sans surprise majeure.
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::io;
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd};
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::process::ExitCode;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use wayland_client::{
|
||||
Connection as ClientConnection, Dispatch as ClientDispatch, EventQueue, Proxy as _,
|
||||
QueueHandle as ClientQueueHandle,
|
||||
backend::Backend as ClientBackend,
|
||||
protocol::{
|
||||
wl_buffer::WlBuffer as ClientBuffer, wl_compositor::WlCompositor as ClientCompositor,
|
||||
wl_registry, wl_shm::WlShm as ClientShm, wl_shm_pool::WlShmPool as ClientShmPool,
|
||||
wl_surface::WlSurface as ClientSurface,
|
||||
},
|
||||
};
|
||||
use wayland_server::{
|
||||
Client, DataInit, Display as ServerDisplay, DisplayHandle, GlobalDispatch,
|
||||
backend::ClientData,
|
||||
protocol::{
|
||||
wl_buffer, wl_callback, wl_compositor, wl_shm, wl_shm_pool, wl_surface,
|
||||
},
|
||||
};
|
||||
|
||||
const W: i32 = 100;
|
||||
const H: i32 = 100;
|
||||
const STRIDE: i32 = W * 4;
|
||||
const POOL_SIZE: i32 = STRIDE * H;
|
||||
const SOCKET_NAME: &str = "wayland-test-06";
|
||||
|
||||
fn expected_pixel(x: i32, y: i32) -> 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))
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Server side
|
||||
// =====================================================================
|
||||
|
||||
#[derive(Default)]
|
||||
struct ServerObservation {
|
||||
surface_created: bool,
|
||||
pool_created_with_fd: Option<i32>,
|
||||
buffer_created: Option<(i32, i32)>, // (w, h)
|
||||
surface_attached: bool,
|
||||
surface_committed: bool,
|
||||
/// Set by main loop after reading pixels successfully
|
||||
pixels_verified: bool,
|
||||
}
|
||||
|
||||
struct ServerState {
|
||||
obs: Arc<Mutex<ServerObservation>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DumbClientData;
|
||||
impl ClientData for DumbClientData {}
|
||||
|
||||
// ---- wl_compositor ----
|
||||
impl GlobalDispatch<wl_compositor::WlCompositor, ()> for ServerState {
|
||||
fn bind(
|
||||
_state: &mut Self,
|
||||
_handle: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: wayland_server::New<wl_compositor::WlCompositor>,
|
||||
_data: &(),
|
||||
data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
data_init.init(resource, ());
|
||||
}
|
||||
}
|
||||
|
||||
impl wayland_server::Dispatch<wl_compositor::WlCompositor, ()> for ServerState {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_client: &Client,
|
||||
_resource: &wl_compositor::WlCompositor,
|
||||
request: wl_compositor::Request,
|
||||
_data: &(),
|
||||
_dh: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
if let wl_compositor::Request::CreateSurface { id } = request {
|
||||
data_init.init(id, ());
|
||||
state.obs.lock().unwrap().surface_created = true;
|
||||
eprintln!("[server] wl_compositor.create_surface");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- wl_shm ----
|
||||
impl GlobalDispatch<wl_shm::WlShm, ()> for ServerState {
|
||||
fn bind(
|
||||
_state: &mut Self,
|
||||
_handle: &DisplayHandle,
|
||||
_client: &Client,
|
||||
resource: wayland_server::New<wl_shm::WlShm>,
|
||||
_data: &(),
|
||||
data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
let shm = data_init.init(resource, ());
|
||||
// Advertise the format
|
||||
shm.format(wl_shm::Format::Argb8888);
|
||||
}
|
||||
}
|
||||
|
||||
impl wayland_server::Dispatch<wl_shm::WlShm, ()> for ServerState {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_client: &Client,
|
||||
_resource: &wl_shm::WlShm,
|
||||
request: wl_shm::Request,
|
||||
_data: &(),
|
||||
_dh: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
if let wl_shm::Request::CreatePool { id, fd, size } = request {
|
||||
let raw_fd = fd.as_raw_fd();
|
||||
// Dup the fd so we keep it after the OwnedFd in the request is dropped
|
||||
let our_fd = unsafe { libc::dup(raw_fd) };
|
||||
state.obs.lock().unwrap().pool_created_with_fd = Some(our_fd);
|
||||
data_init.init(id, PoolData { fd: our_fd, size });
|
||||
eprintln!("[server] wl_shm.create_pool fd={raw_fd} size={size} (server dup={our_fd})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- wl_shm_pool ----
|
||||
struct PoolData {
|
||||
fd: i32,
|
||||
size: i32,
|
||||
}
|
||||
|
||||
impl wayland_server::Dispatch<wl_shm_pool::WlShmPool, PoolData> for ServerState {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_client: &Client,
|
||||
_resource: &wl_shm_pool::WlShmPool,
|
||||
request: wl_shm_pool::Request,
|
||||
_data: &PoolData,
|
||||
_dh: &DisplayHandle,
|
||||
data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
if let wl_shm_pool::Request::CreateBuffer {
|
||||
id,
|
||||
offset,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
format,
|
||||
} = request
|
||||
{
|
||||
data_init.init(id, ());
|
||||
state.obs.lock().unwrap().buffer_created = Some((width, height));
|
||||
eprintln!(
|
||||
"[server] wl_shm_pool.create_buffer offset={offset} {width}x{height} stride={stride} format={format:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- wl_buffer ----
|
||||
impl wayland_server::Dispatch<wl_buffer::WlBuffer, ()> for ServerState {
|
||||
fn request(
|
||||
_state: &mut Self,
|
||||
_client: &Client,
|
||||
_resource: &wl_buffer::WlBuffer,
|
||||
_request: wl_buffer::Request,
|
||||
_data: &(),
|
||||
_dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- wl_surface ----
|
||||
impl wayland_server::Dispatch<wl_surface::WlSurface, ()> for ServerState {
|
||||
fn request(
|
||||
state: &mut Self,
|
||||
_client: &Client,
|
||||
_resource: &wl_surface::WlSurface,
|
||||
request: wl_surface::Request,
|
||||
_data: &(),
|
||||
_dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
match request {
|
||||
wl_surface::Request::Attach { .. } => {
|
||||
state.obs.lock().unwrap().surface_attached = true;
|
||||
eprintln!("[server] wl_surface.attach");
|
||||
}
|
||||
wl_surface::Request::Damage { .. }
|
||||
| wl_surface::Request::DamageBuffer { .. } => {
|
||||
eprintln!("[server] wl_surface.damage");
|
||||
}
|
||||
wl_surface::Request::Commit => {
|
||||
state.obs.lock().unwrap().surface_committed = true;
|
||||
eprintln!("[server] wl_surface.commit");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl wayland_server::Dispatch<wl_callback::WlCallback, ()> for ServerState {
|
||||
fn request(
|
||||
_state: &mut Self,
|
||||
_client: &Client,
|
||||
_resource: &wl_callback::WlCallback,
|
||||
_request: wl_callback::Request,
|
||||
_data: &(),
|
||||
_dh: &DisplayHandle,
|
||||
_data_init: &mut DataInit<'_, Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Client side
|
||||
// =====================================================================
|
||||
|
||||
#[derive(Default)]
|
||||
struct ClientState {
|
||||
compositor: Option<ClientCompositor>,
|
||||
shm: Option<ClientShm>,
|
||||
}
|
||||
|
||||
impl ClientDispatch<wl_registry::WlRegistry, ()> for ClientState {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
registry: &wl_registry::WlRegistry,
|
||||
event: wl_registry::Event,
|
||||
_data: &(),
|
||||
_conn: &ClientConnection,
|
||||
qh: &ClientQueueHandle<Self>,
|
||||
) {
|
||||
if let wl_registry::Event::Global { name, interface, version } = event {
|
||||
eprintln!("[client] global #{name}: {interface} v{version}");
|
||||
match interface.as_str() {
|
||||
"wl_compositor" => {
|
||||
state.compositor = Some(registry.bind(name, version, qh, ()));
|
||||
}
|
||||
"wl_shm" => {
|
||||
state.shm = Some(registry.bind(name, version, qh, ()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! noop_dispatch {
|
||||
($iface:ty) => {
|
||||
impl ClientDispatch<$iface, ()> for ClientState {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_r: &$iface,
|
||||
_event: <$iface as Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &ClientConnection,
|
||||
_qh: &ClientQueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
use wayland_client::Proxy;
|
||||
|
||||
noop_dispatch!(ClientCompositor);
|
||||
noop_dispatch!(ClientShm);
|
||||
noop_dispatch!(ClientShmPool);
|
||||
noop_dispatch!(ClientBuffer);
|
||||
noop_dispatch!(ClientSurface);
|
||||
|
||||
// =====================================================================
|
||||
// Process orchestration
|
||||
// =====================================================================
|
||||
|
||||
unsafe fn create_shm_buffer(name: &str, size: usize) -> Result<(OwnedFd, *mut u8), String> {
|
||||
let cname = CString::new(name).unwrap();
|
||||
let _ = libc::shm_unlink(cname.as_ptr());
|
||||
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: {}", errno_str()));
|
||||
}
|
||||
Ok((unsafe { OwnedFd::from_raw_fd(fd) }, p as *mut u8))
|
||||
}
|
||||
|
||||
use std::os::fd::FromRawFd;
|
||||
|
||||
unsafe fn child_main(server_path: &str) -> ! {
|
||||
eprintln!("[client pid={}] connecting to {server_path}", libc::getpid());
|
||||
|
||||
// Connect to the server socket
|
||||
let stream = match UnixStream::connect(server_path) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("[client] connect: {e}");
|
||||
libc::_exit(2);
|
||||
}
|
||||
};
|
||||
let fd = stream.into_raw_fd();
|
||||
// wayland-client connect_to_env reads WAYLAND_SOCKET
|
||||
std::env::set_var("WAYLAND_SOCKET", fd.to_string());
|
||||
|
||||
let conn = match ClientConnection::connect_to_env() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("[client] connect_to_env: {e}");
|
||||
libc::_exit(3);
|
||||
}
|
||||
};
|
||||
let mut event_queue: EventQueue<ClientState> = conn.new_event_queue();
|
||||
let qh = event_queue.handle();
|
||||
let _registry = conn.display().get_registry(&qh, ());
|
||||
|
||||
let mut state = ClientState::default();
|
||||
// Roundtrip to receive globals
|
||||
if let Err(e) = event_queue.roundtrip(&mut state) {
|
||||
eprintln!("[client] roundtrip 1: {e}");
|
||||
libc::_exit(4);
|
||||
}
|
||||
if state.compositor.is_none() || state.shm.is_none() {
|
||||
eprintln!("[client] missing compositor/shm globals");
|
||||
libc::_exit(5);
|
||||
}
|
||||
eprintln!("[client] got compositor + shm globals");
|
||||
|
||||
// Create the shm buffer
|
||||
let (shm_fd, map) = match create_shm_buffer("/wl-test-06-buffer", POOL_SIZE as usize) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("[client] shm: {e}");
|
||||
libc::_exit(6);
|
||||
}
|
||||
};
|
||||
|
||||
// Paint pattern
|
||||
let pixels = std::slice::from_raw_parts_mut(map as *mut u32, (W * H) as usize);
|
||||
for y in 0..H {
|
||||
for x in 0..W {
|
||||
pixels[(y * W + x) as usize] = expected_pixel(x, y);
|
||||
}
|
||||
}
|
||||
eprintln!("[client] painted {}x{} ARGB pattern in shm", W, H);
|
||||
|
||||
// Create wl_shm_pool from the fd
|
||||
let shm = state.shm.clone().unwrap();
|
||||
let pool = shm.create_pool(shm_fd.as_fd(), POOL_SIZE, &qh, ());
|
||||
|
||||
// Create wl_buffer
|
||||
let buffer = pool.create_buffer(
|
||||
0, W, H, STRIDE,
|
||||
wayland_client::protocol::wl_shm::Format::Argb8888,
|
||||
&qh, (),
|
||||
);
|
||||
|
||||
// Create wl_surface
|
||||
let compositor = state.compositor.clone().unwrap();
|
||||
let surface = compositor.create_surface(&qh, ());
|
||||
|
||||
// attach, damage, commit
|
||||
surface.attach(Some(&buffer), 0, 0);
|
||||
surface.damage_buffer(0, 0, W, H);
|
||||
surface.commit();
|
||||
|
||||
eprintln!("[client] attached + damaged + committed");
|
||||
|
||||
// Flush all pending writes
|
||||
if let Err(e) = event_queue.flush() {
|
||||
eprintln!("[client] flush: {e}");
|
||||
libc::_exit(7);
|
||||
}
|
||||
// Roundtrip to ensure server has processed everything
|
||||
let _ = event_queue.roundtrip(&mut state);
|
||||
|
||||
eprintln!("[client] done, exit 0");
|
||||
libc::_exit(0);
|
||||
}
|
||||
|
||||
fn run() -> Result<(), String> {
|
||||
println!("[test-06] Wayland shm pipeline on Redox");
|
||||
|
||||
// Pick a socket path. We won't use bind_auto because XDG_RUNTIME_DIR
|
||||
// is not guaranteed inside the redoxer mini-VM.
|
||||
let socket_path = format!("/tmp/{SOCKET_NAME}.sock");
|
||||
let _ = std::fs::remove_file(&socket_path);
|
||||
let _ = std::fs::remove_file(format!("/tmp/{SOCKET_NAME}.sock.lock"));
|
||||
|
||||
let listener = UnixListener::bind(&socket_path)
|
||||
.map_err(|e| format!("UnixListener::bind({socket_path}): {e}"))?;
|
||||
listener
|
||||
.set_nonblocking(true)
|
||||
.map_err(|e| format!("set_nonblocking: {e}"))?;
|
||||
println!("[server] listening on {socket_path}");
|
||||
|
||||
let obs = Arc::new(Mutex::new(ServerObservation::default()));
|
||||
|
||||
let mut display: ServerDisplay<ServerState> = ServerDisplay::new()
|
||||
.map_err(|e| format!("Display::new: {e:?}"))?;
|
||||
let mut dh = display.handle();
|
||||
dh.create_global::<ServerState, wl_compositor::WlCompositor, _>(5, ());
|
||||
dh.create_global::<ServerState, wl_shm::WlShm, _>(1, ());
|
||||
|
||||
// Fork the client
|
||||
let pid = unsafe { libc::fork() };
|
||||
if pid < 0 {
|
||||
return Err(format!("fork: {}", errno_str()));
|
||||
}
|
||||
if pid == 0 {
|
||||
unsafe { child_main(&socket_path) };
|
||||
}
|
||||
|
||||
let mut state = ServerState { obs: obs.clone() };
|
||||
let start = Instant::now();
|
||||
let timeout = Duration::from_secs(5);
|
||||
|
||||
let mut accepted_pid: Option<libc::pid_t> = None;
|
||||
|
||||
while start.elapsed() < timeout {
|
||||
// Accept new client
|
||||
if accepted_pid.is_none() {
|
||||
match listener.accept() {
|
||||
Ok((stream, _addr)) => {
|
||||
stream.set_nonblocking(true).ok();
|
||||
dh.insert_client(stream, Arc::new(DumbClientData))
|
||||
.map_err(|e| format!("insert_client: {e:?}"))?;
|
||||
accepted_pid = Some(pid);
|
||||
println!("[server] accepted client connection");
|
||||
}
|
||||
Err(e) if e.kind() == io::ErrorKind::WouldBlock => {}
|
||||
Err(e) => return Err(format!("accept: {e}")),
|
||||
}
|
||||
}
|
||||
|
||||
display
|
||||
.dispatch_clients(&mut state)
|
||||
.map_err(|e| format!("dispatch_clients: {e:?}"))?;
|
||||
display
|
||||
.flush_clients()
|
||||
.map_err(|e| format!("flush_clients: {e:?}"))?;
|
||||
|
||||
let snap = obs.lock().unwrap();
|
||||
if snap.surface_committed && snap.pool_created_with_fd.is_some() {
|
||||
// We have all we need to verify pixels
|
||||
break;
|
||||
}
|
||||
drop(snap);
|
||||
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
|
||||
// Capture observation state
|
||||
let surface_committed;
|
||||
let buffer_created;
|
||||
let surface_attached;
|
||||
let pool_fd;
|
||||
{
|
||||
let s = obs.lock().unwrap();
|
||||
surface_committed = s.surface_committed;
|
||||
buffer_created = s.buffer_created;
|
||||
surface_attached = s.surface_attached;
|
||||
pool_fd = s.pool_created_with_fd;
|
||||
}
|
||||
|
||||
if !surface_committed {
|
||||
return Err("server did not observe wl_surface.commit within 5s".into());
|
||||
}
|
||||
if buffer_created.is_none() {
|
||||
return Err("server did not observe wl_shm_pool.create_buffer".into());
|
||||
}
|
||||
if !surface_attached {
|
||||
return Err("server did not observe wl_surface.attach".into());
|
||||
}
|
||||
let pool_fd = pool_fd.ok_or("server did not receive shm fd")?;
|
||||
|
||||
println!("[server] all expected protocol events observed; mapping fd to verify pixels");
|
||||
|
||||
// Map the received fd and verify pixels
|
||||
let map = unsafe {
|
||||
libc::mmap(
|
||||
ptr::null_mut(),
|
||||
POOL_SIZE as usize,
|
||||
libc::PROT_READ,
|
||||
libc::MAP_SHARED,
|
||||
pool_fd,
|
||||
0,
|
||||
)
|
||||
};
|
||||
if map == libc::MAP_FAILED {
|
||||
return Err(format!("server mmap: {}", errno_str()));
|
||||
}
|
||||
let pixels = unsafe { std::slice::from_raw_parts(map as *const u32, (W * H) as usize) };
|
||||
let mut mismatches: Vec<(i32, i32, u32, u32)> = Vec::new();
|
||||
for y in 0..H {
|
||||
for x in 0..W {
|
||||
let got = pixels[(y * W + x) as usize];
|
||||
let want = expected_pixel(x, y);
|
||||
if got != want && mismatches.len() < 5 {
|
||||
mismatches.push((x, y, got, want));
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe { libc::munmap(map, POOL_SIZE as usize) };
|
||||
if !mismatches.is_empty() {
|
||||
for (x, y, got, want) in &mismatches {
|
||||
eprintln!("[server] mismatch at ({x},{y}): got {got:#010x} want {want:#010x}");
|
||||
}
|
||||
return Err(format!("{} pixel mismatches in {} total", mismatches.len(), W * H));
|
||||
}
|
||||
|
||||
println!(
|
||||
"[server] all {} pixels match expected pattern (read via fd received through Wayland protocol)",
|
||||
W * H
|
||||
);
|
||||
|
||||
// Reap child
|
||||
let mut status: libc::c_int = 0;
|
||||
unsafe { libc::waitpid(pid, &mut status, 0) };
|
||||
let exit_code = unsafe { libc::WEXITSTATUS(status) };
|
||||
if exit_code != 0 {
|
||||
return Err(format!("child exited with status {exit_code}"));
|
||||
}
|
||||
println!("[server] child reaped cleanly");
|
||||
|
||||
// Cleanup
|
||||
let _ = std::fs::remove_file(&socket_path);
|
||||
let _ = std::fs::remove_file(format!("{socket_path}.lock"));
|
||||
let cname = CString::new("/wl-test-06-buffer").unwrap();
|
||||
unsafe { libc::shm_unlink(cname.as_ptr()) };
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
Ok(()) => {
|
||||
println!("[test-06] PASS: full Wayland shm pipeline works on Redox");
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[test-06] FAIL: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue