milestone: desktop path Phases 1-5

Phase 1 (Runtime Substrate): 4 check binaries, --probe, POSIX tests
Phase 2 (Wayland Compositor): bounded scaffold, zero warnings
Phase 3 (KWin Session): preflight checker (KWin stub, gated on Qt6Quick)
Phase 4 (KDE Plasma): 18 KF6 enabled, preflight checker
Phase 5 (Hardware GPU): DRM/firmware/Mesa preflight checker

Build: zero warnings, all scripts syntax-clean. Oracle-verified.
This commit is contained in:
2026-04-29 09:54:06 +01:00
parent b23714f542
commit 8acc73d774
508 changed files with 76526 additions and 396 deletions
@@ -0,0 +1,18 @@
[package]
name = "redoxerd"
description = "Redoxer QEMU daemon"
version = "0.1.0"
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
edition = "2018"
[dependencies]
anyhow.workspace = true
libredox.workspace = true
redox_event.workspace = true
redox_syscall.workspace = true
redox_termios.workspace = true
common = { path = "../common" }
qemu-exit = "3.0.2"
[lints]
workspace = true
@@ -0,0 +1,184 @@
use anyhow::Context;
use std::fs::{self, OpenOptions};
use std::io;
use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
use std::process::{Child, Command, ExitStatus, Stdio};
mod sys;
const DEFAULT_COLS: u32 = 80;
const DEFAULT_LINES: u32 = 30;
event::user_data! {
enum EventData {
Pty,
Timer,
}
}
fn handle(
event_queue: event::EventQueue<EventData>,
master_fd: RawFd,
timeout_fd: RawFd,
process: &mut Child,
) -> io::Result<ExitStatus> {
let handle_event = |event: EventData| -> io::Result<bool> {
match event {
EventData::Pty => {
let mut packet = [0; 4096];
loop {
// Read data from PTY master
let count = match libredox::call::read(master_fd as usize, &mut packet) {
Ok(0) => return Ok(false),
Ok(count) => count,
Err(ref err) if err.errno() == libredox::errno::EAGAIN => return Ok(true),
Err(err) => return Err(err.into()),
};
// Write data to stdout
libredox::call::write(1, &packet[1..count])?;
for i in 1..count {
// Write byte to QEMU debugcon (Bochs compatible)
sys::debug_char(packet[i]);
}
}
}
EventData::Timer => {
let mut timespec = syscall::TimeSpec::default();
libredox::call::read(timeout_fd as usize, &mut timespec)?;
timespec.tv_sec += 1;
libredox::call::write(timeout_fd as usize, &mut timespec)?;
Ok(true)
}
}
};
if handle_event(EventData::Pty)? && handle_event(EventData::Timer)? {
'events: loop {
match process.try_wait() {
Ok(status_opt) => match status_opt {
Some(status) => return Ok(status),
None => (),
},
Err(err) => match err.kind() {
io::ErrorKind::WouldBlock => (),
_ => return Err(err),
},
}
let event = event_queue.next_event()?;
if !handle_event(event.user_data)? {
break 'events;
}
}
}
let _ = process.kill();
process.wait()
}
fn getpty(columns: u32, lines: u32) -> io::Result<(RawFd, String)> {
let master = libredox::call::open(
"/scheme/pty",
libredox::flag::O_CLOEXEC
| libredox::flag::O_RDWR
| libredox::flag::O_CREAT
| libredox::flag::O_NONBLOCK,
0,
)?;
if let Ok(winsize_fd) = libredox::call::dup(master, b"winsize") {
let _ = libredox::call::write(
winsize_fd,
&redox_termios::Winsize {
ws_row: lines as u16,
ws_col: columns as u16,
},
);
let _ = libredox::call::close(winsize_fd);
}
let mut buf: [u8; 4096] = [0; 4096];
let count = libredox::call::fpath(master, &mut buf)?;
Ok((master as RawFd, unsafe {
String::from_utf8_unchecked(Vec::from(&buf[..count]))
}))
}
fn inner() -> anyhow::Result<()> {
common::acquire_port_io_rights()?;
let config = fs::read_to_string("/etc/redoxerd").context("Failed to read /etc/redoxerd")?;
let mut config_lines = config.lines();
let (columns, lines) = (DEFAULT_COLS, DEFAULT_LINES);
let (master_fd, pty) = getpty(columns, lines)?;
let timeout_fd = libredox::call::open(
"/scheme/time/4",
libredox::flag::O_CLOEXEC | libredox::flag::O_RDWR | libredox::flag::O_NONBLOCK,
0,
)? as RawFd;
let event_queue = event::EventQueue::new()?;
event_queue.subscribe(master_fd as usize, EventData::Pty, event::EventFlags::READ)?;
event_queue.subscribe(
timeout_fd as usize,
EventData::Timer,
event::EventFlags::READ,
)?;
let slave_stdin = OpenOptions::new().read(true).open(&pty)?;
let slave_stdout = OpenOptions::new().write(true).open(&pty)?;
let slave_stderr = OpenOptions::new().write(true).open(&pty)?;
let Some(name) = config_lines.next() else {
anyhow::bail!("/etc/redoxerd does not specify command");
};
let mut command = Command::new(name);
for arg in config_lines {
command.arg(arg);
}
unsafe {
command
.stdin(Stdio::from_raw_fd(slave_stdin.into_raw_fd()))
.stdout(Stdio::from_raw_fd(slave_stdout.into_raw_fd()))
.stderr(Stdio::from_raw_fd(slave_stderr.into_raw_fd()))
.env("COLUMNS", format!("{}", columns))
.env("LINES", format!("{}", lines))
.env("TERM", "xterm-256color")
.env("TTY", &pty);
}
let mut process = command
.spawn()
.with_context(|| format!("Failed to spawn {command:?}"))?;
let status = handle(event_queue, master_fd, timeout_fd, &mut process)
.with_context(|| format!("Failed to run {name}"))?;
if status.success() {
Ok(())
} else {
anyhow::bail!("{name} failed with {}", status);
}
}
fn main() {
match inner() {
Ok(()) => {
// Exit with success using qemu device
sys::exit_success();
}
Err(err) => {
eprintln!("redoxerd: {:#}", err);
// Wait a bit for the error message to get flushed through the tty subsystem before exiting.
std::thread::sleep(std::time::Duration::from_millis(100));
// Exit with error using qemu device
sys::exit_failure();
}
}
}
@@ -0,0 +1,69 @@
pub fn exit_success() {
imp::exit(true);
}
pub fn exit_failure() {
imp::exit(false);
}
pub fn debug_char(b: u8) {
let _ = imp::write_debug(b);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod imp {
use syscall::Io;
use syscall::Pio;
pub fn exit(success: bool) {
if success {
Pio::<u16>::new(0x604).write(0x2000);
Pio::<u8>::new(0x501).write(51 / 2);
} else {
Pio::<u8>::new(0x501).write(53 / 2);
}
}
pub fn write_debug(b: u8) -> syscall::Result<()> {
Pio::<u8>::new(0xe9).write(b);
Ok(())
}
}
#[cfg(target_arch = "aarch64")]
mod imp {
use qemu_exit::QEMUExit;
pub fn exit(success: bool) {
let q = qemu_exit::AArch64::new();
if success {
q.exit(51)
} else {
q.exit(53)
}
}
pub fn write_debug(b: u8) -> syscall::Result<()> {
// TODO
Ok(())
}
}
#[cfg(target_arch = "riscv64")]
mod imp {
pub fn exit(success: bool) {
todo!()
// let q = qemu_exit::RISCV64::new(addr);
// if success {
// q.exit(51)
// } else {
// q.exit(53)
// }
}
pub fn write_debug(b: u8) -> syscall::Result<()> {
// TODO
Ok(())
}
}