fix: update custom recipe implementations

Update cpufreqd, driver-params, firmware-loader, seatd, redox-drm,
and redox-driver-sys. Add XHCI controller quirk table. Update
redbear-compositor protocol handlers.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-05-11 10:10:25 +01:00
parent f36b6a4582
commit 9b0fb0de0c
16 changed files with 2076 additions and 33 deletions
@@ -15,8 +15,8 @@ mkdir -p "${COOKBOOK_STAGE}/usr/lib"
-j "${COOKBOOK_MAKE_JOBS}" \
${COOKBOOK_CARGO_FLAGS[@]}
cp "${COOKBOOK_SOURCE}/target/${TARGET}/release/libredox_driver_sys.a" \
cp "${COOKBOOK_BUILD}/target/${TARGET}/release/libredox_driver_sys.a" \
"${COOKBOOK_STAGE}/usr/lib/libredox_driver_sys.a"
cp "${COOKBOOK_SOURCE}/target/${TARGET}/release/libredox_driver_sys.rlib" \
cp "${COOKBOOK_BUILD}/target/${TARGET}/release/libredox_driver_sys.rlib" \
"${COOKBOOK_STAGE}/usr/lib/libredox_driver_sys.rlib"
"""
@@ -0,0 +1,449 @@
use super::{XhciControllerQuirk, XhciControllerQuirkFlags};
const INTEL_BASE: XhciControllerQuirkFlags = XhciControllerQuirkFlags::from_bits_truncate(
XhciControllerQuirkFlags::INTEL_HOST.bits()
| XhciControllerQuirkFlags::LPM_SUPPORT.bits()
| XhciControllerQuirkFlags::AVOID_BEI.bits(),
);
const AMD_LIMIT_AND_PLL: XhciControllerQuirkFlags = XhciControllerQuirkFlags::from_bits_truncate(
XhciControllerQuirkFlags::LIMIT_EP_INTERVAL_9.bits()
| XhciControllerQuirkFlags::AMD_PLL_FIX.bits(),
);
// Linux 7.0 `drivers/usb/host/xhci-pci.c` mined table.
//
// Fresco Logic revision-specific quirks are collapsed to the device level here because the
// Red Bear controller lookup surface is keyed by vendor/device only.
pub const XHCI_CONTROLLER_QUIRK_TABLE: &[XhciControllerQuirk] = &[
XhciControllerQuirk {
vendor: 0x8086,
device: 0x1e31,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x1e31,
flags: XhciControllerQuirkFlags::SPURIOUS_REBOOT,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x8c31,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x9c31,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x9c31,
flags: XhciControllerQuirkFlags::SPURIOUS_REBOOT,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x9cb1,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x9cb1,
flags: XhciControllerQuirkFlags::SPURIOUS_REBOOT,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x22b5,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x22b5,
flags: XhciControllerQuirkFlags::PME_STUCK,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0xa12f,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0xa12f,
flags: XhciControllerQuirkFlags::PME_STUCK,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x9d2f,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x9d2f,
flags: XhciControllerQuirkFlags::PME_STUCK,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x0aa8,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x0aa8,
flags: XhciControllerQuirkFlags::PME_STUCK,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x1aa8,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x1aa8,
flags: XhciControllerQuirkFlags::PME_STUCK,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x5aa8,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x5aa8,
flags: XhciControllerQuirkFlags::PME_STUCK,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x19d0,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x19d0,
flags: XhciControllerQuirkFlags::PME_STUCK,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x8a13,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x9a13,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0xa0ed,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0xa3af,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0xa3af,
flags: XhciControllerQuirkFlags::PME_STUCK,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x51ed,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x54ed,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x1138,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x15b5,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x15b6,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x15c1,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x15db,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x15d4,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x15e9,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x15ec,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x8086,
device: 0x15f0,
flags: INTEL_BASE,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x13ed,
flags: AMD_LIMIT_AND_PLL,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x13ee,
flags: AMD_LIMIT_AND_PLL,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x145c,
flags: XhciControllerQuirkFlags::AMD_PLL_FIX,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x145c,
flags: XhciControllerQuirkFlags::SUSPEND_DELAY,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x148c,
flags: AMD_LIMIT_AND_PLL,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x15d4,
flags: AMD_LIMIT_AND_PLL,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x15d5,
flags: AMD_LIMIT_AND_PLL,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x15e0,
flags: AMD_LIMIT_AND_PLL,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x15e0,
flags: XhciControllerQuirkFlags::SUSPEND_DELAY,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x15e1,
flags: AMD_LIMIT_AND_PLL,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x15e1,
flags: XhciControllerQuirkFlags::SUSPEND_DELAY,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x15e5,
flags: AMD_LIMIT_AND_PLL,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x15e5,
flags: XhciControllerQuirkFlags::DISABLE_SPARSE,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x15e5,
flags: XhciControllerQuirkFlags::RESET_ON_RESUME,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x1639,
flags: XhciControllerQuirkFlags::AMD_PLL_FIX,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x43b9,
flags: XhciControllerQuirkFlags::AMD_PLL_FIX,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x43b9,
flags: XhciControllerQuirkFlags::NO_SOFT_RETRY,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x43ba,
flags: XhciControllerQuirkFlags::AMD_PLL_FIX,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x43bb,
flags: XhciControllerQuirkFlags::AMD_PLL_FIX,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x43bb,
flags: XhciControllerQuirkFlags::SUSPEND_DELAY,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x43bb,
flags: XhciControllerQuirkFlags::NO_SOFT_RETRY,
},
XhciControllerQuirk {
vendor: 0x1022,
device: 0x43bc,
flags: XhciControllerQuirkFlags::AMD_PLL_FIX,
},
XhciControllerQuirk {
vendor: 0x1002,
device: 0x7316,
flags: XhciControllerQuirkFlags::LIMIT_EP_INTERVAL_9,
},
XhciControllerQuirk {
vendor: 0x1b73,
device: 0x1000,
flags: XhciControllerQuirkFlags::BROKEN_STREAMS,
},
XhciControllerQuirk {
vendor: 0x1b73,
device: 0x1000,
flags: XhciControllerQuirkFlags::BROKEN_MSI,
},
XhciControllerQuirk {
vendor: 0x1b73,
device: 0x1000,
flags: XhciControllerQuirkFlags::RESET_EP_QUIRK,
},
XhciControllerQuirk {
vendor: 0x1b73,
device: 0x1000,
flags: XhciControllerQuirkFlags::SLOW_SUSPEND,
},
XhciControllerQuirk {
vendor: 0x1b73,
device: 0x1009,
flags: XhciControllerQuirkFlags::BROKEN_STREAMS,
},
XhciControllerQuirk {
vendor: 0x1b73,
device: 0x1400,
flags: XhciControllerQuirkFlags::BROKEN_MSI,
},
XhciControllerQuirk {
vendor: 0x1b6f,
device: 0x7023,
flags: XhciControllerQuirkFlags::RESET_ON_RESUME,
},
XhciControllerQuirk {
vendor: 0x1b6f,
device: 0x7023,
flags: XhciControllerQuirkFlags::BROKEN_STREAMS,
},
XhciControllerQuirk {
vendor: 0x1b6f,
device: 0x7023,
flags: XhciControllerQuirkFlags::NO_SOFT_RETRY,
},
XhciControllerQuirk {
vendor: 0x1b6f,
device: 0x7052,
flags: XhciControllerQuirkFlags::RESET_ON_RESUME,
},
XhciControllerQuirk {
vendor: 0x1b6f,
device: 0x7052,
flags: XhciControllerQuirkFlags::BROKEN_STREAMS,
},
XhciControllerQuirk {
vendor: 0x1b6f,
device: 0x7052,
flags: XhciControllerQuirkFlags::NO_SOFT_RETRY,
},
XhciControllerQuirk {
vendor: 0x1b21,
device: 0x1042,
flags: XhciControllerQuirkFlags::SPURIOUS_SUCCESS,
},
XhciControllerQuirk {
vendor: 0x1b21,
device: 0x1042,
flags: XhciControllerQuirkFlags::BROKEN_STREAMS,
},
XhciControllerQuirk {
vendor: 0x1b21,
device: 0x1142,
flags: XhciControllerQuirkFlags::NO_64BIT_SUPPORT,
},
XhciControllerQuirk {
vendor: 0x1b21,
device: 0x1242,
flags: XhciControllerQuirkFlags::NO_64BIT_SUPPORT,
},
XhciControllerQuirk {
vendor: 0x1b21,
device: 0x2142,
flags: XhciControllerQuirkFlags::NO_64BIT_SUPPORT,
},
XhciControllerQuirk {
vendor: 0x1b21,
device: 0x3042,
flags: XhciControllerQuirkFlags::RESET_ON_RESUME,
},
XhciControllerQuirk {
vendor: 0x1b21,
device: 0x3242,
flags: XhciControllerQuirkFlags::NO_64BIT_SUPPORT,
},
XhciControllerQuirk {
vendor: 0x1912,
device: 0x0014,
flags: XhciControllerQuirkFlags::ZERO_64B_REGS,
},
XhciControllerQuirk {
vendor: 0x1912,
device: 0x0015,
flags: XhciControllerQuirkFlags::ZERO_64B_REGS,
},
XhciControllerQuirk {
vendor: 0x1912,
device: 0x0015,
flags: XhciControllerQuirkFlags::RESET_ON_RESUME,
},
XhciControllerQuirk {
vendor: 0x1106,
device: 0x3432,
flags: XhciControllerQuirkFlags::RESET_ON_RESUME,
},
XhciControllerQuirk {
vendor: 0x1106,
device: 0x3432,
flags: XhciControllerQuirkFlags::BROKEN_STREAMS,
},
XhciControllerQuirk {
vendor: 0x1106,
device: 0x3483,
flags: XhciControllerQuirkFlags::RESET_ON_RESUME,
},
XhciControllerQuirk {
vendor: 0x1106,
device: 0x3483,
flags: XhciControllerQuirkFlags::TRB_OVERFETCH,
},
];
@@ -11,7 +11,7 @@ libredox = "0.1"
redox_syscall = { version = "0.7", features = ["std"] }
syscall04 = { package = "redox_syscall", version = "0.4" }
redox-scheme = "0.11"
daemon = { path = "../../../../../recipes/core/base/source/daemon" }
daemon = { path = "daemon" }
log = "0.4"
thiserror = "2"
bitflags = "2"
@@ -0,0 +1,11 @@
[package]
name = "daemon"
description = "Redox daemon library"
version = "0.0.0"
edition = "2021"
[dependencies]
libc = "0.2"
libredox = "0.1"
redox-scheme = "0.11"
redox_syscall = { version = "0.7", features = ["std"] }
@@ -0,0 +1,149 @@
//! A library for creating and managing daemons for RedoxOS.
#![feature(never_type)]
use std::io::{self, PipeWriter, Read, Write};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::process::CommandExt;
use std::process::Command;
use libredox::Fd;
use redox_scheme::Socket;
use redox_scheme::scheme::{SchemeAsync, SchemeSync};
unsafe fn get_fd(var: &str) -> Option<RawFd> {
let fd: RawFd = match std::env::var(var)
.map_err(|e| eprintln!("daemon: env var {var} not set: {e}"))
.ok()
.and_then(|val| {
val.parse()
.map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}"))
.ok()
}) {
Some(fd) => fd,
None => return None,
};
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 {
eprintln!(
"daemon: failed to set CLOEXEC flag for {var} fd: {}",
io::Error::last_os_error()
);
return None;
}
Some(fd)
}
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
cmd.env(env, format!("{}", fd.as_raw_fd()));
unsafe {
cmd.pre_exec(move || {
// Pass notify pipe to child
if libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, 0) == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
});
}
}
/// A long running background process that handles requests.
#[must_use = "Daemon::ready must be called"]
pub struct Daemon {
write_pipe: Option<PipeWriter>,
}
impl Daemon {
/// Create a new daemon.
pub fn new(f: impl FnOnce(Daemon) -> !) -> ! {
let write_pipe = unsafe { get_fd("INIT_NOTIFY").map(|fd| io::PipeWriter::from_raw_fd(fd)) };
f(Daemon { write_pipe })
}
/// Notify the process that the daemon is ready to accept requests.
pub fn ready(mut self) {
if let Some(write_pipe) = self.write_pipe.as_mut() {
if let Err(err) = write_pipe.write_all(&[0]) {
if err.kind() != io::ErrorKind::BrokenPipe {
eprintln!("daemon::ready write failed: {err}");
}
}
}
}
/// Executes `Command` as a child process.
// FIXME remove once the service spawning of hwd and pcid-spawner is moved to init
#[deprecated]
pub fn spawn(mut cmd: Command) {
let (mut read_pipe, write_pipe) = io::pipe().unwrap();
unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) };
if let Err(err) = cmd.spawn() {
eprintln!("daemon: failed to execute {cmd:?}: {err}");
return;
}
let mut data = [0];
match read_pipe.read_exact(&mut data) {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
eprintln!("daemon: {cmd:?} exited without notifying readiness");
}
Err(err) => {
eprintln!("daemon: failed to wait for {cmd:?}: {err}");
}
}
}
}
/// A long running background process that handles requests using schemes.
#[must_use = "SchemeDaemon::ready must be called"]
pub struct SchemeDaemon {
write_pipe: Option<PipeWriter>,
}
impl SchemeDaemon {
/// Create a new daemon for use with schemes.
pub fn new(f: impl FnOnce(SchemeDaemon) -> !) -> ! {
let write_pipe = unsafe { get_fd("INIT_NOTIFY").map(|fd| io::PipeWriter::from_raw_fd(fd)) };
f(SchemeDaemon { write_pipe })
}
/// Notify the process that the scheme daemon is ready to accept requests.
pub fn ready_with_fd(self, cap_fd: Fd) -> syscall::Result<()> {
let cap_raw = cap_fd.into_raw();
if let Some(write_pipe) = self.write_pipe {
syscall::call_wo(
write_pipe.as_raw_fd() as usize,
&cap_raw.to_ne_bytes(),
syscall::CallFlags::FD,
&[],
)?;
}
Ok(())
}
/// Notify the process that the synchronous scheme daemon is ready to accept requests.
pub fn ready_sync_scheme<S: SchemeSync>(
self,
socket: &Socket,
scheme: &mut S,
) -> syscall::Result<()> {
let cap_id = scheme.scheme_root()?;
let cap_fd = socket.create_this_scheme_fd(0, cap_id, 0, 0)?;
self.ready_with_fd(Fd::new(cap_fd))
}
/// Notify the process that the asynchronous scheme daemon is ready to accept requests.
pub fn ready_async_scheme<S: SchemeAsync>(
self,
socket: &Socket,
scheme: &mut S,
) -> syscall::Result<()> {
let cap_id = scheme.scheme_root()?;
let cap_fd = socket.create_this_scheme_fd(0, cap_id, 0, 0)?;
self.ready_with_fd(Fd::new(cap_fd))
}
}
@@ -9,4 +9,3 @@ path = "src/main.rs"
[dependencies]
redox_syscall = "0.7"
log = "0.4"
@@ -2,7 +2,6 @@ use std::env;
use std::fs;
use std::io::{Read, Write};
use std::time::{Duration, Instant};
use log::{info, warn, LevelFilter};
const IA32_PERF_CTL: u32 = 0x199;
const POLL_MS: u64 = 100;
@@ -14,13 +13,6 @@ const THERMAL_CACHE_MS: u64 = 1000;
#[derive(Clone, Copy, PartialEq, Debug)]
enum Governor { Performance, Powersave, Ondemand, Conservative, Schedutil }
struct StderrLogger;
impl log::Log for StderrLogger {
fn enabled(&self, m: &log::Metadata) -> bool { m.level() <= LevelFilter::Info }
fn log(&self, r: &log::Record) { eprintln!("[{}] cpufreqd: {}", r.level(), r.args()); }
fn flush(&self) {}
}
#[derive(Clone)]
struct PState { freq_khz: u32, power_mw: u32, latency_us: u32, ctl: u64 }
@@ -120,18 +112,16 @@ fn write_scheme_state(governor: Governor, cpus: &[CpuInfo]) {
}
fn main() {
log::set_logger(&StderrLogger).ok();
log::set_max_level(LevelFilter::Info);
let governor = match env::var("CPUFREQ_GOVERNOR").unwrap_or_default().as_str() {
"performance" => Governor::Performance, "powersave" => Governor::Powersave,
"conservative" => Governor::Conservative, "schedutil" => Governor::Schedutil,
_ => Governor::Ondemand,
};
let cpus = detect_cpus();
info!("detected {} CPU(s), governor={:?}", cpus.len(), governor);
eprintln!("[INFO] cpufreqd: detected {} CPU(s), governor={:?}", cpus.len(), governor);
let mut ci: Vec<CpuInfo> = cpus.iter().map(|&id| {
let ps = read_acpi_pss(id);
info!("CPU{}: {} P-states ({} - {} kHz)", id, ps.len(), ps.first().map_or(0, |p| p.freq_khz), ps.last().map_or(0, |p| p.freq_khz));
eprintln!("[INFO] cpufreqd: CPU{}: {} P-states ({} - {} kHz)", id, ps.len(), ps.first().map_or(0, |p| p.freq_khz), ps.last().map_or(0, |p| p.freq_khz));
CpuInfo { id, pstates: ps, current_idx: 0, load_history: [0.0; SAMPLE_WINDOW], load_idx: 0, throttle: false, msr_errors: 0, msr_suppressed: false }
}).collect();
let mut prev: Vec<(u64, u64)> = vec![(0, 0); cpus.len()];
@@ -149,11 +139,11 @@ fn main() {
if n != c.current_idx && n < c.pstates.len() {
let ct = c.pstates[n].ctl;
if write_msr(c.id, IA32_PERF_CTL, ct) {
info!("CPU{}: P{}→P{} ({}→{} kHz, load={:.0}%)", c.id, c.current_idx, n, c.pstates[c.current_idx].freq_khz, c.pstates[n].freq_khz, l * 100.0);
eprintln!("[INFO] cpufreqd: CPU{}: P{}→P{} ({}{} kHz, load={:.0}%)", c.id, c.current_idx, n, c.pstates[c.current_idx].freq_khz, c.pstates[n].freq_khz, l * 100.0);
c.current_idx = n; c.msr_errors = 0; c.msr_suppressed = false;
} else {
c.msr_errors += 1;
if !c.msr_suppressed { warn!("CPU{}: MSR write failed ({}/{})", c.id, c.msr_errors, MSR_ERROR_SUPPRESS_COUNT); if c.msr_errors >= MSR_ERROR_SUPPRESS_COUNT { c.msr_suppressed = true; } }
if !c.msr_suppressed { eprintln!("[WARN] cpufreqd: CPU{}: MSR write failed ({}/{})", c.id, c.msr_errors, MSR_ERROR_SUPPRESS_COUNT); if c.msr_errors >= MSR_ERROR_SUPPRESS_COUNT { c.msr_suppressed = true; } }
}
}
}
@@ -538,11 +538,9 @@ fn bound_path_from_env() -> PathBuf {
}
#[cfg(target_os = "redox")]
fn init_notify_fd() -> std::result::Result<i32, String> {
let raw = env::var("INIT_NOTIFY")
.map_err(|_| "driver-params: INIT_NOTIFY not set".to_string())?;
raw.parse::<i32>()
.map_err(|_| "driver-params: INIT_NOTIFY is not a valid fd".to_string())
fn init_notify_fd() -> Option<i32> {
let raw = env::var("INIT_NOTIFY").ok()?;
raw.parse::<i32>().ok()
}
#[cfg(target_os = "redox")]
@@ -570,14 +568,16 @@ fn notify_scheme_ready(
#[cfg(target_os = "redox")]
fn run_daemon(bound_path: PathBuf) -> std::result::Result<(), String> {
let notify_fd = init_notify_fd()?;
let notify_fd = init_notify_fd();
let socket = Socket::create()
.map_err(|err| format!("driver-params: failed to create scheme socket: {err}"))?;
let mut state = SchemeState::new();
let mut scheme = DriverParamsScheme::new(bound_path);
scheme.read_driver_manager_bound();
notify_scheme_ready(notify_fd, &socket, &mut scheme)?;
if let Some(fd) = notify_fd {
notify_scheme_ready(fd, &socket, &mut scheme)?;
}
libredox::call::setrens(0, 0)
.map_err(|err| format!("driver-params: failed to enter null namespace: {err}"))?;
@@ -51,11 +51,9 @@ fn default_firmware_dir() -> PathBuf {
#[cfg(target_os = "redox")]
unsafe fn get_init_notify_fd() -> Option<RawFd> {
let Ok(value) = env::var("INIT_NOTIFY") else {
eprintln!("firmware-loader: INIT_NOTIFY not set; readiness notification disabled");
return None;
};
let Ok(fd) = value.parse::<RawFd>() else {
eprintln!("firmware-loader: INIT_NOTIFY is not a valid fd; readiness notification disabled");
return None;
};
libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC);
+10 -6
View File
@@ -1,13 +1,17 @@
#TODO not compiled or tested
[source]
git = "https://github.com/jackpot51/seatd"
branch = "redox"
tar = "https://git.sr.ht/~kennylevinsen/seatd/archive/0.9.1.tar.gz"
blake3 = "0bca2188cb2f56d7d50774657435db9e5ead358854cfbde8c4d7d6504929a49f"
[build]
template = "meson"
dependencies = [
"expat",
]
mesonflags = [
"-Dlibseat-seatd=enabled",
"-Dlibseat-builtin=disabled",
"-Dlibseat-logind=disabled",
"-Dserver=enabled",
"-Dexamples=disabled",
"-Dman-pages=disabled",
]
[package.files]
"/usr/bin/seatd" = "/usr/bin/seatd"
@@ -0,0 +1,483 @@
fn map_framebuffer(_phys: usize, size: usize) -> Vec<u8> {
vec![0u8; size]
}
pub struct DrawBufferTarget {
pub ptr: *mut u8,
pub len: usize,
pub stride: usize,
}
pub struct DisplayBackend {
drm: Option<drm_backend::DrmOutput>,
fallback: Vec<u8>,
width: u32,
height: u32,
stride: u32,
}
impl DisplayBackend {
pub fn open_or_framebuffer() -> Self {
if let Some(drm) = drm_backend::DrmOutput::open() {
eprintln!(
"redbear-compositor: using DRM/KMS output {}x{}",
drm.width, drm.height
);
return Self {
width: drm.width,
height: drm.height,
stride: drm.stride,
drm: Some(drm),
fallback: Vec::new(),
};
}
let width: u32 = std::env::var("FRAMEBUFFER_WIDTH")
.unwrap_or_else(|_| "1280".into())
.parse()
.unwrap_or(1280);
let height: u32 = std::env::var("FRAMEBUFFER_HEIGHT")
.unwrap_or_else(|_| "720".into())
.parse()
.unwrap_or(720);
let stride: u32 = std::env::var("FRAMEBUFFER_STRIDE")
.unwrap_or_else(|_| (width * 4).to_string())
.parse()
.unwrap_or(width * 4);
let fb_phys_str = std::env::var("FRAMEBUFFER_ADDR").unwrap_or_else(|_| "0x80000000".into());
let fb_phys =
usize::from_str_radix(fb_phys_str.trim_start_matches("0x"), 16).unwrap_or(0x80000000);
eprintln!(
"redbear-compositor: fb {}x{} stride {} phys 0x{:X}",
width, height, stride, fb_phys
);
Self {
drm: None,
fallback: map_framebuffer(fb_phys, height as usize * stride as usize),
width,
height,
stride,
}
}
pub fn dimensions(&self) -> (u32, u32, u32) {
(self.width, self.height, self.stride)
}
pub fn draw_target(&mut self) -> Option<DrawBufferTarget> {
if let Some(drm) = self.drm.as_mut() {
return drm.draw_target();
}
Some(DrawBufferTarget {
ptr: self.fallback.as_mut_ptr(),
len: self.fallback.len(),
stride: self.stride as usize,
})
}
pub fn submit(&self) {
if let Some(drm) = self.drm.as_ref() {
drm.submit();
}
}
}
// ── DRM/KMS backend ──
// Uses /scheme/drm/card0 for hardware-accelerated display output.
// Cross-referenced with Linux DRM KMS API (drm_mode.h).
// I/O: writes [u64_le ioctl_code][payload] to the scheme file, reads response.
#[cfg(target_os = "redox")]
mod drm_backend {
use super::DrawBufferTarget;
use std::fs::File;
use std::io::{Read, Write};
use std::sync::atomic::{AtomicUsize, Ordering};
const DRM_IOCTL_BASE: usize = 0x00A0;
const DRM_IOCTL_MODE_GETCONNECTOR: usize = DRM_IOCTL_BASE + 7;
const DRM_IOCTL_MODE_SETCRTC: usize = DRM_IOCTL_BASE + 2;
const DRM_IOCTL_MODE_CREATE_DUMB: usize = DRM_IOCTL_BASE + 18;
const DRM_IOCTL_MODE_MAP_DUMB: usize = DRM_IOCTL_BASE + 19;
const DRM_IOCTL_MODE_ADDFB: usize = DRM_IOCTL_BASE + 21;
const DRM_IOCTL_MODE_PAGE_FLIP: usize = DRM_IOCTL_BASE + 16;
fn drm_ioctl(file: &mut File, code: usize, req: &[u8], resp: &mut [u8]) -> std::io::Result<()> {
let mut wbuf = Vec::with_capacity(8 + req.len());
wbuf.extend_from_slice(&(code as u64).to_le_bytes());
wbuf.extend_from_slice(req);
file.write_all(&wbuf)?;
if !resp.is_empty() {
file.read_exact(resp)?;
}
Ok(())
}
#[repr(C)]
#[derive(Clone, Copy)]
struct DrmResources {
connector_count: u32,
crtc_count: u32,
encoder_count: u32,
}
#[repr(C)]
struct DrmConnector {
connector_id: u32,
connection: u32,
connector_type: u32,
mm_width: u32,
mm_height: u32,
encoder_id: u32,
mode_count: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct DrmModeInfo {
clock: u32,
hdisplay: u16,
hsync_start: u16,
hsync_end: u16,
htotal: u16,
hskew: u16,
vdisplay: u16,
vsync_start: u16,
vsync_end: u16,
vtotal: u16,
vscan: u16,
vrefresh: u32,
flags: u32,
type_: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct DrmGetEncoder {
encoder_id: u32,
encoder_type: u32,
crtc_id: u32,
possible_crtcs: u32,
possible_clones: u32,
}
#[repr(C)]
struct DrmCreateDumb {
height: u32,
width: u32,
bpp: u32,
flags: u32,
handle: u32,
pitch: u32,
size: u64,
}
#[repr(C)]
struct DrmMapDumb {
handle: u32,
pad: u32,
offset: u64,
}
#[repr(C)]
struct DrmAddFb {
width: u32,
height: u32,
pitch: u32,
bpp: u32,
depth: u32,
handle: u32,
fb_id: u32,
}
#[repr(C)]
struct DrmSetCrtc {
crtc_id: u32,
fb_handle: u32,
connector_count: u32,
connectors: [u32; 8],
mode: DrmModeInfo,
}
pub struct DrmOutput {
pub width: u32,
pub height: u32,
pub stride: u32,
pub buffers: Vec<(usize, usize)>,
fb_ids: Vec<u32>,
pub current: AtomicUsize,
_file: File,
}
impl DrmOutput {
pub fn open() -> Option<Self> {
let mut file = File::open("/scheme/drm/card0").ok()?;
eprintln!("redbear-compositor: opened /scheme/drm/card0");
let mut resources_resp = vec![0u8; std::mem::size_of::<DrmResources>() + 32];
if drm_ioctl(&mut file, DRM_IOCTL_BASE, &[], &mut resources_resp).is_err() {
return None;
}
let resources = unsafe { *(resources_resp.as_ptr() as *const DrmResources) };
if resources.connector_count == 0 {
return None;
}
let connector_id = unsafe {
*(resources_resp
.as_ptr()
.add(std::mem::size_of::<DrmResources>()) as *const u32)
};
if connector_id == 0 {
return None;
}
let mut conn: DrmConnector = unsafe { std::mem::zeroed() };
conn.connector_id = connector_id;
conn.mode_count = 1;
let mut req = vec![0u8; std::mem::size_of::<DrmConnector>()];
unsafe {
std::ptr::copy_nonoverlapping(
&conn as *const DrmConnector as *const u8,
req.as_mut_ptr(),
req.len(),
);
}
let mut resp =
vec![0u8; std::mem::size_of::<DrmConnector>() + std::mem::size_of::<DrmModeInfo>()];
if drm_ioctl(&mut file, DRM_IOCTL_MODE_GETCONNECTOR, &req, &mut resp).is_err() {
return None;
}
unsafe {
std::ptr::copy_nonoverlapping(
resp.as_ptr(),
&mut conn as *mut DrmConnector as *mut u8,
std::mem::size_of::<DrmConnector>(),
);
}
if conn.mode_count == 0 {
return None;
}
let mode = unsafe {
&*(resp.as_ptr().add(std::mem::size_of::<DrmConnector>()) as *const DrmModeInfo)
};
let width = mode.hdisplay as u32;
let height = mode.vdisplay as u32;
eprintln!("redbear-compositor: DRM mode {}x{}", width, height);
let mut crtc_id = 1u32;
if conn.encoder_id != 0 {
let mut encoder_req: DrmGetEncoder = unsafe { std::mem::zeroed() };
encoder_req.encoder_id = conn.encoder_id;
let mut encoder_req_buf = vec![0u8; std::mem::size_of::<DrmGetEncoder>()];
unsafe {
std::ptr::copy_nonoverlapping(
&encoder_req as *const DrmGetEncoder as *const u8,
encoder_req_buf.as_mut_ptr(),
encoder_req_buf.len(),
);
}
let mut encoder_resp = vec![0u8; std::mem::size_of::<DrmGetEncoder>()];
if drm_ioctl(
&mut file,
DRM_IOCTL_MODE_GETCONNECTOR - 1,
&encoder_req_buf,
&mut encoder_resp,
)
.is_ok()
{
let encoder = unsafe { *(encoder_resp.as_ptr() as *const DrmGetEncoder) };
if encoder.crtc_id != 0 {
crtc_id = encoder.crtc_id;
}
}
}
let mut buffers = Vec::new();
let mut fb_ids = Vec::new();
let mut stride = 0u32;
for _ in 0..2 {
let mut dumb: DrmCreateDumb = unsafe { std::mem::zeroed() };
dumb.height = height;
dumb.width = width;
dumb.bpp = 32;
let mut dumb_req = vec![0u8; std::mem::size_of::<DrmCreateDumb>()];
unsafe {
std::ptr::copy_nonoverlapping(
&dumb as *const DrmCreateDumb as *const u8,
dumb_req.as_mut_ptr(),
dumb_req.len(),
);
}
let mut dumb_resp = vec![0u8; std::mem::size_of::<DrmCreateDumb>()];
if drm_ioctl(
&mut file,
DRM_IOCTL_MODE_CREATE_DUMB,
&dumb_req,
&mut dumb_resp,
)
.is_err()
{
return None;
}
unsafe {
std::ptr::copy_nonoverlapping(
dumb_resp.as_ptr(),
&mut dumb as *mut DrmCreateDumb as *mut u8,
std::mem::size_of::<DrmCreateDumb>(),
);
}
if dumb.handle == 0 {
return None;
}
stride = dumb.pitch;
let mut map = DrmMapDumb {
handle: dumb.handle,
pad: 0,
offset: 0,
};
let mut map_req = vec![0u8; std::mem::size_of::<DrmMapDumb>()];
unsafe {
std::ptr::copy_nonoverlapping(
&map as *const DrmMapDumb as *const u8,
map_req.as_mut_ptr(),
map_req.len(),
);
}
let mut map_resp = vec![0u8; std::mem::size_of::<DrmMapDumb>()];
if drm_ioctl(&mut file, DRM_IOCTL_MODE_MAP_DUMB, &map_req, &mut map_resp).is_err() {
return None;
}
unsafe {
std::ptr::copy_nonoverlapping(
map_resp.as_ptr(),
&mut map as *mut DrmMapDumb as *mut u8,
std::mem::size_of::<DrmMapDumb>(),
);
}
let buf_size = dumb.size as usize;
if map.offset == 0 {
return None;
}
buffers.push((map.offset as usize, buf_size));
let mut addfb = DrmAddFb {
width,
height,
pitch: stride,
bpp: 32,
depth: 24,
handle: dumb.handle,
fb_id: 0,
};
let mut addfb_req = vec![0u8; std::mem::size_of::<DrmAddFb>()];
unsafe {
std::ptr::copy_nonoverlapping(
&addfb as *const DrmAddFb as *const u8,
addfb_req.as_mut_ptr(),
addfb_req.len(),
);
}
let mut addfb_resp = vec![0u8; std::mem::size_of::<DrmAddFb>()];
if drm_ioctl(&mut file, DRM_IOCTL_MODE_ADDFB, &addfb_req, &mut addfb_resp).is_err()
{
return None;
}
unsafe {
std::ptr::copy_nonoverlapping(
addfb_resp.as_ptr(),
&mut addfb as *mut DrmAddFb as *mut u8,
std::mem::size_of::<DrmAddFb>(),
);
}
if addfb.fb_id == 0 {
return None;
}
fb_ids.push(addfb.fb_id);
}
let mut setcrtc: DrmSetCrtc = unsafe { std::mem::zeroed() };
setcrtc.crtc_id = crtc_id;
setcrtc.fb_handle = fb_ids[0];
setcrtc.connector_count = 1;
setcrtc.connectors[0] = connector_id;
setcrtc.mode = *mode;
let mut setcrtc_req = vec![0u8; std::mem::size_of::<DrmSetCrtc>()];
unsafe {
std::ptr::copy_nonoverlapping(
&setcrtc as *const DrmSetCrtc as *const u8,
setcrtc_req.as_mut_ptr(),
setcrtc_req.len(),
);
}
if drm_ioctl(&mut file, DRM_IOCTL_MODE_SETCRTC, &setcrtc_req, &mut []).is_err() {
return None;
}
eprintln!(
"redbear-compositor: DRM output {}x{} stride={} connector={} crtc={}",
width, height, stride, connector_id, crtc_id
);
Some(DrmOutput {
width,
height,
stride,
buffers,
fb_ids,
current: AtomicUsize::new(0),
_file: file,
})
}
pub fn flip(&self) {
if self.fb_ids.len() < 2 {
return;
}
let cur = self.current.load(Ordering::Relaxed);
let next = (cur + 1) % self.fb_ids.len();
let fb_id = self.fb_ids[next];
let mut buf = Vec::with_capacity(12);
buf.extend_from_slice(&(DRM_IOCTL_MODE_PAGE_FLIP as u64).to_le_bytes());
buf.extend_from_slice(&fb_id.to_le_bytes());
if let Ok(mut f) = File::open("/scheme/drm/card0") {
let _ = f.write_all(&buf);
}
self.current.store(next, Ordering::Relaxed);
}
pub fn submit(&self) {
self.flip();
}
pub(super) fn draw_target(&mut self) -> Option<DrawBufferTarget> {
if self.buffers.is_empty() {
return None;
}
let idx = (self.current.load(Ordering::Relaxed) + 1) % self.buffers.len();
let (addr, len) = self.buffers[idx];
Some(DrawBufferTarget {
ptr: addr as *mut u8,
len,
stride: self.stride as usize,
})
}
}
}
#[cfg(not(target_os = "redox"))]
mod drm_backend {
use super::DrawBufferTarget;
pub struct DrmOutput {
pub width: u32,
pub height: u32,
pub stride: u32,
}
impl DrmOutput {
pub fn open() -> Option<Self> {
None
}
pub fn submit(&self) {}
pub(super) fn draw_target(&mut self) -> Option<DrawBufferTarget> {
None
}
}
}
@@ -0,0 +1,388 @@
use std::os::unix::net::UnixStream;
use crate::protocol;
use crate::protocol::*;
use crate::state::{
DataDeviceState, DataSourceState, ShellSurfaceKind, ShellSurfaceState, SubsurfaceState,
SurfaceRole,
};
use crate::wire::read_wayland_string;
use crate::Compositor;
impl Compositor {
pub fn handle_wl_shell(
&self,
client_id: u32,
opcode: u16,
payload: &[u8],
) -> Result<(), String> {
match opcode {
WL_SHELL_GET_SHELL_SURFACE => {
if payload.len() >= 8 {
let new_id =
u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
let surface_id =
u32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]);
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
client.objects.insert(new_id, OBJECT_TYPE_WL_SHELL_SURFACE);
client.shell_surfaces.insert(
new_id,
ShellSurfaceState {
object_id: new_id,
surface_id,
..ShellSurfaceState::default()
},
);
if let Some(surface) = client.surfaces.get_mut(&surface_id) {
surface.role = Some(SurfaceRole::Shell(ShellSurfaceState {
object_id: new_id,
surface_id,
..ShellSurfaceState::default()
}));
}
}
}
Ok(())
}
_ => Err(format!(
"redbear-compositor: unhandled opcode {} on wl_shell",
opcode
)),
}
}
pub fn handle_wl_shell_surface(
&self,
client_id: u32,
object_id: u32,
opcode: u16,
payload: &[u8],
) -> Result<(), String> {
match opcode {
protocol::WL_SHELL_SURFACE_SET_TOPLEVEL
| protocol::WL_SHELL_SURFACE_PONG
| protocol::WL_SHELL_SURFACE_SET_TRANSIENT
| protocol::WL_SHELL_SURFACE_SET_FULLSCREEN
| protocol::WL_SHELL_SURFACE_SET_POPUP
| protocol::WL_SHELL_SURFACE_SET_MAXIMIZED
| protocol::WL_SHELL_SURFACE_SET_TITLE
| protocol::WL_SHELL_SURFACE_SET_CLASS
| protocol::WL_SHELL_SURFACE_MOVE => {
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
if let Some(shell_surface) = client.shell_surfaces.get_mut(&object_id) {
match opcode {
protocol::WL_SHELL_SURFACE_SET_TOPLEVEL => {
shell_surface.kind = ShellSurfaceKind::Toplevel;
}
protocol::WL_SHELL_SURFACE_PONG if payload.len() >= 4 => {
shell_surface.last_ping_serial = Some(u32::from_le_bytes([
payload[0], payload[1], payload[2], payload[3],
]));
}
protocol::WL_SHELL_SURFACE_SET_TRANSIENT if payload.len() >= 16 => {
shell_surface.kind = ShellSurfaceKind::Transient;
let parent_id = u32::from_le_bytes([
payload[0], payload[1], payload[2], payload[3],
]);
shell_surface.parent_surface_id =
(parent_id != 0).then_some(parent_id);
}
protocol::WL_SHELL_SURFACE_SET_FULLSCREEN => {
shell_surface.kind = ShellSurfaceKind::Fullscreen;
}
protocol::WL_SHELL_SURFACE_SET_POPUP if payload.len() >= 20 => {
shell_surface.kind = ShellSurfaceKind::Popup;
let parent_id = u32::from_le_bytes([
payload[0], payload[1], payload[2], payload[3],
]);
shell_surface.parent_surface_id =
(parent_id != 0).then_some(parent_id);
shell_surface.popup_serial = Some(u32::from_le_bytes([
payload[16],
payload[17],
payload[18],
payload[19],
]));
}
protocol::WL_SHELL_SURFACE_SET_MAXIMIZED => {
shell_surface.kind = ShellSurfaceKind::Maximized;
}
protocol::WL_SHELL_SURFACE_SET_TITLE => {
let mut cursor = 0;
if let Ok(title) = read_wayland_string(payload, &mut cursor) {
shell_surface.title = Some(title);
}
}
protocol::WL_SHELL_SURFACE_SET_CLASS => {
let mut cursor = 0;
if let Ok(class) = read_wayland_string(payload, &mut cursor) {
shell_surface.class = Some(class);
}
}
_ => {}
}
if let Some(surface) = client.surfaces.get_mut(&shell_surface.surface_id) {
surface.role = Some(SurfaceRole::Shell(shell_surface.clone()));
}
}
}
Ok(())
}
_ => Err(format!(
"redbear-compositor: unhandled opcode {} on object {}",
opcode, object_id
)),
}
}
pub fn handle_wl_data_device_manager(
&self,
client_id: u32,
opcode: u16,
payload: &[u8],
) -> Result<(), String> {
match opcode {
WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE => {
if payload.len() >= 4 {
let new_id =
u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
client.objects.insert(new_id, OBJECT_TYPE_WL_DATA_SOURCE);
client
.data_sources
.insert(new_id, DataSourceState::default());
}
}
Ok(())
}
WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE => {
if payload.len() >= 4 {
let new_id =
u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
client.objects.insert(new_id, OBJECT_TYPE_WL_DATA_DEVICE);
client
.data_devices
.insert(new_id, DataDeviceState::default());
}
}
Ok(())
}
_ => Err(format!(
"redbear-compositor: unhandled data_device_manager opcode {}",
opcode
)),
}
}
pub fn handle_wl_data_source(
&self,
client_id: u32,
object_id: u32,
opcode: u16,
payload: &[u8],
stream: &mut UnixStream,
) -> Result<(), String> {
match opcode {
WL_DATA_SOURCE_DESTROY => {
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
client.objects.remove(&object_id);
client.data_sources.remove(&object_id);
}
drop(clients);
self.send_delete_id(stream, object_id);
Ok(())
}
WL_DATA_SOURCE_OFFER | WL_DATA_SOURCE_SET_ACTIONS => {
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
if let Some(source) = client.data_sources.get_mut(&object_id) {
match opcode {
WL_DATA_SOURCE_OFFER => {
let mut cursor = 0;
if let Ok(mime) = read_wayland_string(payload, &mut cursor) {
if !mime.is_empty() {
source.mime_types.push(mime);
}
}
}
WL_DATA_SOURCE_SET_ACTIONS if payload.len() >= 8 => {
source.actions = Some(u32::from_le_bytes([
payload[0], payload[1], payload[2], payload[3],
]));
}
_ => {}
}
}
}
Ok(())
}
_ => Err(format!(
"redbear-compositor: unhandled data_source opcode {} on object {}",
opcode, object_id
)),
}
}
pub fn handle_wl_data_device(
&self,
client_id: u32,
object_id: u32,
opcode: u16,
payload: &[u8],
stream: &mut UnixStream,
) -> Result<(), String> {
match opcode {
WL_DATA_DEVICE_RELEASE => {
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
client.objects.remove(&object_id);
client.data_devices.remove(&object_id);
}
drop(clients);
self.send_delete_id(stream, object_id);
Ok(())
}
WL_DATA_DEVICE_START_DRAG | WL_DATA_DEVICE_SET_SELECTION => {
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
if let Some(device) = client.data_devices.get_mut(&object_id) {
match opcode {
WL_DATA_DEVICE_START_DRAG if payload.len() >= 4 => {
let source_id = u32::from_le_bytes([
payload[0], payload[1], payload[2], payload[3],
]);
device.drag_source = (source_id != 0).then_some(source_id);
}
WL_DATA_DEVICE_SET_SELECTION if payload.len() >= 4 => {
let source_id = u32::from_le_bytes([
payload[0], payload[1], payload[2], payload[3],
]);
device.selection_source = (source_id != 0).then_some(source_id);
}
_ => {}
}
}
}
Ok(())
}
_ => Err(format!(
"redbear-compositor: unhandled data_device opcode {} on object {}",
opcode, object_id
)),
}
}
pub fn handle_wl_subcompositor(
&self,
client_id: u32,
opcode: u16,
payload: &[u8],
) -> Result<(), String> {
match opcode {
WL_SUBCOMPOSITOR_GET_SUBSURFACE => {
if payload.len() >= 12 {
let new_id =
u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
let surface_id =
u32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]);
let parent_surface_id =
u32::from_le_bytes([payload[8], payload[9], payload[10], payload[11]]);
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
client.objects.insert(new_id, OBJECT_TYPE_WL_SUBSURFACE);
client.subsurfaces.insert(
new_id,
SubsurfaceState {
surface_id,
parent_surface_id,
sync: true,
..SubsurfaceState::default()
},
);
Compositor::stack_surface_relative(
client,
surface_id,
parent_surface_id,
true,
);
}
}
Ok(())
}
_ => Err(format!(
"redbear-compositor: unhandled subcompositor opcode {}",
opcode
)),
}
}
pub fn handle_wl_subsurface(
&self,
client_id: u32,
object_id: u32,
opcode: u16,
payload: &[u8],
stream: &mut UnixStream,
) -> Result<(), String> {
if opcode == WL_SUBSURFACE_DESTROY {
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
client.objects.remove(&object_id);
client.subsurfaces.remove(&object_id);
}
drop(clients);
self.send_delete_id(stream, object_id);
return Ok(());
}
let mut clients = self.clients.lock().unwrap();
if let Some(client) = clients.get_mut(&client_id) {
let mut restack: Option<(u32, u32, bool)> = None;
if let Some(subsurface) = client.subsurfaces.get_mut(&object_id) {
match opcode {
protocol::WL_SUBSURFACE_SET_POSITION if payload.len() >= 8 => {
subsurface.x =
i32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
subsurface.y =
i32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]);
if let Some(surface) = client.surfaces.get_mut(&subsurface.surface_id) {
surface.x = subsurface.x.max(0) as u32;
surface.y = subsurface.y.max(0) as u32;
}
}
protocol::WL_SUBSURFACE_SET_SYNC => subsurface.sync = true,
protocol::WL_SUBSURFACE_SET_DESYNC => subsurface.sync = false,
protocol::WL_SUBSURFACE_PLACE_ABOVE | protocol::WL_SUBSURFACE_PLACE_BELOW
if payload.len() >= 4 =>
{
let sibling_surface_id =
u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
restack = Some((
subsurface.surface_id,
sibling_surface_id,
opcode == protocol::WL_SUBSURFACE_PLACE_ABOVE,
));
}
_ => {}
}
}
if let Some((surface_id, sibling_surface_id, place_above)) = restack {
Compositor::stack_surface_relative(
client,
surface_id,
sibling_surface_id,
place_above,
);
}
}
Ok(())
}
}
@@ -0,0 +1,177 @@
pub const WL_DISPLAY_SYNC: u16 = 0;
pub const WL_DISPLAY_GET_REGISTRY: u16 = 1;
#[allow(dead_code)]
pub const WL_DISPLAY_ERROR: u16 = 0;
pub const WL_DISPLAY_DELETE_ID: u16 = 2;
pub const WL_REGISTRY_BIND: u16 = 0;
pub const WL_REGISTRY_GLOBAL: u16 = 0;
pub const WL_REGISTRY_GLOBAL_REMOVE: u16 = 1;
pub const WL_FIXES_DESTROY: u16 = 0;
pub const WL_FIXES_DESTROY_REGISTRY: u16 = 1;
pub const WL_FIXES_ACK_GLOBAL_REMOVE: u16 = 2;
pub const WL_COMPOSITOR_CREATE_SURFACE: u16 = 0;
pub const WL_COMPOSITOR_CREATE_REGION: u16 = 1;
pub const WL_SHM_CREATE_POOL: u16 = 0;
pub const WL_SHM_RELEASE: u16 = 1;
pub const WL_SHM_FORMAT: u16 = 0;
pub const WL_SHM_POOL_CREATE_BUFFER: u16 = 0;
pub const WL_SHM_POOL_DESTROY: u16 = 1;
pub const WL_SHM_POOL_RESIZE: u16 = 2;
pub const WL_BUFFER_DESTROY: u16 = 0;
pub const WL_BUFFER_RELEASE: u16 = 0;
pub const WL_SURFACE_DESTROY: u16 = 0;
pub const WL_SURFACE_ATTACH: u16 = 1;
pub const WL_SURFACE_DAMAGE: u16 = 2;
pub const WL_SURFACE_FRAME: u16 = 3;
pub const WL_SURFACE_SET_OPAQUE_REGION: u16 = 4;
pub const WL_SURFACE_SET_INPUT_REGION: u16 = 5;
pub const WL_SURFACE_COMMIT: u16 = 6;
pub const WL_REGION_DESTROY: u16 = 0;
pub const WL_REGION_ADD: u16 = 1;
pub const WL_REGION_SUBTRACT: u16 = 2;
pub const WL_SHELL_GET_SHELL_SURFACE: u16 = 0;
pub const WL_SHELL_SURFACE_PONG: u16 = 0;
pub const WL_SHELL_SURFACE_MOVE: u16 = 1;
pub const WL_SHELL_SURFACE_SET_TOPLEVEL: u16 = 2;
pub const WL_SHELL_SURFACE_SET_TRANSIENT: u16 = 3;
pub const WL_SHELL_SURFACE_SET_FULLSCREEN: u16 = 4;
pub const WL_SHELL_SURFACE_SET_POPUP: u16 = 5;
pub const WL_SHELL_SURFACE_SET_MAXIMIZED: u16 = 6;
#[allow(dead_code)]
pub const WL_SHELL_SURFACE_PING: u16 = 0;
#[allow(dead_code)]
pub const WL_SHELL_SURFACE_CONFIGURE: u16 = 1;
pub const WL_SHELL_SURFACE_SET_TITLE: u16 = 8;
pub const WL_SHELL_SURFACE_SET_CLASS: u16 = 9;
pub const XDG_WM_BASE_DESTROY: u16 = 0;
pub const XDG_WM_BASE_CREATE_POSITIONER: u16 = 1;
pub const XDG_WM_BASE_GET_XDG_SURFACE: u16 = 2;
pub const XDG_WM_BASE_PONG: u16 = 3;
pub const XDG_WM_BASE_PING: u16 = 0;
pub const XDG_SURFACE_DESTROY: u16 = 0;
pub const XDG_SURFACE_GET_TOPLEVEL: u16 = 1;
pub const XDG_SURFACE_GET_POPUP: u16 = 2;
pub const XDG_SURFACE_SET_WINDOW_GEOMETRY: u16 = 3;
pub const XDG_SURFACE_ACK_CONFIGURE: u16 = 4;
pub const XDG_SURFACE_CONFIGURE: u16 = 0;
pub const XDG_TOPLEVEL_CONFIGURE: u16 = 0;
pub const XDG_TOPLEVEL_DESTROY: u16 = 0;
pub const XDG_TOPLEVEL_SET_PARENT: u16 = 1;
pub const XDG_TOPLEVEL_SET_TITLE: u16 = 2;
pub const XDG_TOPLEVEL_SET_APP_ID: u16 = 3;
pub const XDG_TOPLEVEL_SHOW_WINDOW_MENU: u16 = 4;
pub const XDG_TOPLEVEL_MOVE: u16 = 5;
pub const XDG_TOPLEVEL_RESIZE: u16 = 6;
pub const XDG_TOPLEVEL_SET_MAX_SIZE: u16 = 7;
pub const XDG_TOPLEVEL_SET_MIN_SIZE: u16 = 8;
pub const XDG_TOPLEVEL_SET_MAXIMIZED: u16 = 9;
pub const XDG_TOPLEVEL_UNSET_MAXIMIZED: u16 = 10;
pub const XDG_TOPLEVEL_SET_FULLSCREEN: u16 = 11;
pub const XDG_TOPLEVEL_UNSET_FULLSCREEN: u16 = 12;
pub const XDG_TOPLEVEL_SET_MINIMIZED: u16 = 13;
pub const XDG_POSITIONER_DESTROY: u16 = 0;
pub const XDG_POSITIONER_SET_SIZE: u16 = 1;
pub const XDG_POSITIONER_SET_ANCHOR_RECT: u16 = 2;
pub const XDG_POSITIONER_SET_ANCHOR: u16 = 3;
pub const XDG_POSITIONER_SET_GRAVITY: u16 = 4;
pub const XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT: u16 = 5;
pub const XDG_POSITIONER_SET_OFFSET: u16 = 6;
pub const XDG_POPUP_DESTROY: u16 = 0;
pub const XDG_POPUP_GRAB: u16 = 1;
pub const XDG_POPUP_REPOSITION: u16 = 2;
pub const XDG_POPUP_CONFIGURE: u16 = 0;
pub const WL_SEAT_GET_POINTER: u16 = 0;
pub const WL_SEAT_GET_KEYBOARD: u16 = 1;
pub const WL_SEAT_GET_TOUCH: u16 = 2;
pub const WL_SEAT_RELEASE: u16 = 3;
pub const WL_SEAT_CAPABILITIES: u16 = 0;
pub const WL_SEAT_NAME: u16 = 1;
pub const WL_POINTER_RELEASE: u16 = 0;
pub const WL_POINTER_ENTER: u16 = 0;
pub const WL_POINTER_LEAVE: u16 = 1;
pub const WL_KEYBOARD_RELEASE: u16 = 0;
pub const WL_TOUCH_RELEASE: u16 = 0;
pub const WL_KEYBOARD_ENTER: u16 = 1;
pub const WL_KEYBOARD_LEAVE: u16 = 2;
pub const WL_KEYBOARD_MODIFIERS: u16 = 4;
pub const WL_KEYBOARD_REPEAT_INFO: u16 = 5;
pub const WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP: u32 = 0;
pub const WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE: u16 = 0;
pub const WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE: u16 = 1;
pub const WL_DATA_SOURCE_OFFER: u16 = 0;
pub const WL_DATA_SOURCE_DESTROY: u16 = 1;
pub const WL_DATA_SOURCE_SET_ACTIONS: u16 = 2;
pub const WL_DATA_DEVICE_START_DRAG: u16 = 0;
pub const WL_DATA_DEVICE_SET_SELECTION: u16 = 1;
pub const WL_DATA_DEVICE_RELEASE: u16 = 2;
#[allow(dead_code)]
pub const WL_KEYBOARD_KEYMAP: u16 = 0;
#[allow(dead_code)]
pub const WL_KEYBOARD_KEY: u16 = 3;
pub const WL_OUTPUT_GEOMETRY: u16 = 0;
pub const WL_OUTPUT_MODE: u16 = 1;
pub const WL_OUTPUT_DONE: u16 = 2;
pub const WL_OUTPUT_SCALE: u16 = 3;
pub const WL_OUTPUT_RELEASE: u16 = 0;
pub const WL_OUTPUT_NAME: u16 = 4;
pub const WL_OUTPUT_DESCRIPTION: u16 = 5;
pub const WL_CALLBACK_DONE: u16 = 0;
pub const WL_SHM_FORMAT_XRGB8888: u32 = 1;
pub const WL_SHM_FORMAT_ARGB8888: u32 = 0;
pub const OBJECT_TYPE_WL_DISPLAY: u32 = 1;
pub const OBJECT_TYPE_WL_REGISTRY: u32 = 2;
pub const OBJECT_TYPE_WL_COMPOSITOR: u32 = 3;
pub const OBJECT_TYPE_WL_SHM: u32 = 4;
pub const OBJECT_TYPE_WL_SHELL: u32 = 5;
pub const OBJECT_TYPE_WL_SEAT: u32 = 6;
pub const OBJECT_TYPE_WL_OUTPUT: u32 = 7;
pub const OBJECT_TYPE_XDG_WM_BASE: u32 = 8;
pub const OBJECT_TYPE_WL_SURFACE: u32 = 9;
pub const OBJECT_TYPE_WL_BUFFER: u32 = 10;
pub const OBJECT_TYPE_WL_SHELL_SURFACE: u32 = 11;
pub const OBJECT_TYPE_XDG_SURFACE: u32 = 12;
pub const OBJECT_TYPE_XDG_TOPLEVEL: u32 = 13;
pub const OBJECT_TYPE_WL_SHM_POOL: u32 = 14;
pub const OBJECT_TYPE_WL_POINTER: u32 = 15;
pub const OBJECT_TYPE_WL_KEYBOARD: u32 = 16;
pub const OBJECT_TYPE_WL_DATA_DEVICE_MANAGER: u32 = 17;
pub const OBJECT_TYPE_WL_SUBCOMPOSITOR: u32 = 18;
pub const OBJECT_TYPE_WL_DATA_DEVICE: u32 = 19;
pub const OBJECT_TYPE_WL_SUBSURFACE: u32 = 20;
pub const OBJECT_TYPE_WL_FIXES: u32 = 21;
pub const OBJECT_TYPE_WL_REGION: u32 = 22;
pub const OBJECT_TYPE_WL_TOUCH: u32 = 23;
pub const OBJECT_TYPE_WL_DATA_SOURCE: u32 = 24;
pub const OBJECT_TYPE_XDG_POSITIONER: u32 = 25;
pub const OBJECT_TYPE_XDG_POPUP: u32 = 26;
pub const WL_SUBCOMPOSITOR_GET_SUBSURFACE: u16 = 1;
pub const WL_SUBSURFACE_DESTROY: u16 = 0;
pub const WL_SUBSURFACE_SET_POSITION: u16 = 1;
pub const WL_SUBSURFACE_PLACE_ABOVE: u16 = 2;
pub const WL_SUBSURFACE_PLACE_BELOW: u16 = 3;
pub const WL_SUBSURFACE_SET_SYNC: u16 = 4;
pub const WL_SUBSURFACE_SET_DESYNC: u16 = 5;
@@ -0,0 +1,198 @@
use std::collections::{HashMap, HashSet};
pub struct Global {
pub name: u32,
pub interface: String,
pub version: u32,
}
pub struct ShmPool {
pub file: std::fs::File,
pub size: usize,
}
#[derive(Clone)]
pub struct Buffer {
pub pool_id: u32,
pub offset: u32,
pub width: u32,
pub height: u32,
pub stride: u32,
pub _format: u32,
}
#[derive(Clone)]
pub struct Surface {
pub buffer: Option<Buffer>,
pub pending_buffer_id: Option<u32>,
pub committed_buffer_id: Option<u32>,
pub x: u32,
pub y: u32,
pub _width: u32,
pub _height: u32,
pub geometry: Option<WindowGeometry>,
pub role: Option<SurfaceRole>,
pub mapped: bool,
}
#[derive(Clone, Copy)]
pub struct WindowGeometry {
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
}
#[derive(Clone, Default)]
pub struct ConfigureState {
pub pending_serial: Option<u32>,
pub last_acked_serial: Option<u32>,
pub configured: bool,
}
impl ConfigureState {
pub fn note_sent(&mut self, serial: u32) {
self.pending_serial = Some(serial);
}
pub fn ack(&mut self, serial: u32) {
self.last_acked_serial = Some(serial);
if self.pending_serial == Some(serial) {
self.configured = true;
}
}
pub fn can_present(&self) -> bool {
self.pending_serial.is_none() || self.configured
}
}
#[derive(Clone, Default)]
pub struct ToplevelState {
pub object_id: u32,
pub parent_id: Option<u32>,
pub title: Option<String>,
pub app_id: Option<String>,
pub min_size: Option<(i32, i32)>,
pub max_size: Option<(i32, i32)>,
pub maximized: bool,
pub fullscreen: bool,
pub minimized: bool,
pub configure: ConfigureState,
}
#[derive(Clone, Default)]
pub struct PopupState {
pub object_id: u32,
pub parent_id: Option<u32>,
pub positioner_id: Option<u32>,
pub grab_serial: Option<u32>,
pub configure: ConfigureState,
}
#[derive(Clone)]
pub enum SurfaceRole {
Toplevel(ToplevelState),
Popup(PopupState),
Shell(ShellSurfaceState),
}
impl SurfaceRole {
pub fn can_present(&self) -> bool {
match self {
Self::Toplevel(state) => state.configure.can_present(),
Self::Popup(state) => {
state.object_id != 0
&& state.parent_id.is_some()
&& state.positioner_id.is_some()
&& state.configure.can_present()
}
Self::Shell(state) => state.object_id != 0,
}
}
pub fn ack_configure(&mut self, serial: u32) {
match self {
Self::Toplevel(state) => state.configure.ack(serial),
Self::Popup(state) => state.configure.ack(serial),
Self::Shell(_) => {
let _ = serial;
}
}
}
}
#[derive(Clone, Copy, Default)]
pub enum ShellSurfaceKind {
#[default]
None,
Toplevel,
Popup,
Transient,
Fullscreen,
Maximized,
}
#[derive(Clone, Default)]
pub struct ShellSurfaceState {
pub object_id: u32,
pub surface_id: u32,
pub kind: ShellSurfaceKind,
pub title: Option<String>,
pub class: Option<String>,
pub parent_surface_id: Option<u32>,
pub popup_serial: Option<u32>,
pub last_ping_serial: Option<u32>,
}
#[derive(Clone, Default)]
pub struct PositionerState {
pub size: Option<(i32, i32)>,
pub anchor_rect: Option<(i32, i32, i32, i32)>,
pub anchor: Option<u32>,
pub gravity: Option<u32>,
pub constraint_adjustment: Option<u32>,
pub offset: Option<(i32, i32)>,
}
#[derive(Clone, Default)]
pub struct DataSourceState {
pub mime_types: Vec<String>,
pub actions: Option<u32>,
}
#[derive(Clone, Default)]
pub struct DataDeviceState {
pub selection_source: Option<u32>,
pub drag_source: Option<u32>,
}
#[derive(Clone, Default)]
pub struct SubsurfaceState {
pub surface_id: u32,
pub parent_surface_id: u32,
pub x: i32,
pub y: i32,
pub sync: bool,
}
pub struct ClientState {
pub objects: HashMap<u32, u32>,
pub object_versions: HashMap<u32, u32>,
pub surfaces: HashMap<u32, Surface>,
pub surface_order: Vec<u32>,
pub buffers: HashMap<u32, (u32, Buffer)>,
pub shm_pools: HashMap<u32, ShmPool>,
pub positioners: HashMap<u32, PositionerState>,
pub shell_surfaces: HashMap<u32, ShellSurfaceState>,
pub data_sources: HashMap<u32, DataSourceState>,
pub data_devices: HashMap<u32, DataDeviceState>,
pub subsurfaces: HashMap<u32, SubsurfaceState>,
pub keyboard_object_id: Option<u32>,
pub pointer_object_id: Option<u32>,
pub touch_object_id: Option<u32>,
pub keyboard_focus_surface: Option<u32>,
pub pointer_focus_surface: Option<u32>,
pub acked_global_removals: HashSet<u32>,
pub _next_id: u32,
}
@@ -0,0 +1,197 @@
use std::collections::VecDeque;
use std::io::Write;
use std::mem;
use std::os::fd::{AsRawFd, RawFd};
use std::os::unix::net::UnixStream;
pub fn push_u32(buf: &mut Vec<u8>, value: u32) {
buf.extend_from_slice(&value.to_le_bytes());
}
pub fn push_i32(buf: &mut Vec<u8>, value: i32) {
buf.extend_from_slice(&value.to_le_bytes());
}
pub fn push_header(buf: &mut Vec<u8>, object_id: u32, opcode: u16, payload_len: usize) {
push_u32(buf, object_id);
let size = (8 + payload_len) as u32;
push_u32(buf, (size << 16) | u32::from(opcode));
}
pub fn pad_to_4(buf: &mut Vec<u8>) {
while buf.len() % 4 != 0 {
buf.push(0);
}
}
pub fn push_wayland_string(buf: &mut Vec<u8>, value: &str) {
let bytes = value.as_bytes();
push_u32(buf, (bytes.len() + 1) as u32);
buf.extend_from_slice(bytes);
buf.push(0);
pad_to_4(buf);
}
pub fn read_u32(data: &[u8], cursor: &mut usize) -> Result<u32, String> {
if *cursor + 4 > data.len() {
return Err(String::from("unexpected end of message while reading u32"));
}
let value = u32::from_le_bytes([
data[*cursor],
data[*cursor + 1],
data[*cursor + 2],
data[*cursor + 3],
]);
*cursor += 4;
Ok(value)
}
pub fn read_wayland_string(data: &[u8], cursor: &mut usize) -> Result<String, String> {
if *cursor + 4 > data.len() {
return Err(String::from(
"unexpected end of message while reading string length",
));
}
let length = u32::from_le_bytes([
data[*cursor],
data[*cursor + 1],
data[*cursor + 2],
data[*cursor + 3],
]) as usize;
*cursor += 4;
if length == 0 {
return Ok(String::new());
}
if *cursor + length > data.len() {
return Err(String::from(
"unexpected end of message while reading string",
));
}
let bytes = &data[*cursor..*cursor + length];
let string_len = bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(bytes.len());
*cursor += length;
while *cursor % 4 != 0 {
*cursor += 1;
}
std::str::from_utf8(&bytes[..string_len])
.map(str::to_owned)
.map_err(|err| format!("invalid UTF-8 in Wayland string: {err}"))
}
pub fn recv_with_rights(
stream: &mut UnixStream,
data: &mut [u8],
) -> std::io::Result<(usize, VecDeque<RawFd>)> {
let mut iov = libc::iovec {
iov_base: data.as_mut_ptr().cast(),
iov_len: data.len(),
};
let mut control = [0u8; 256];
let mut header = libc::msghdr {
msg_name: std::ptr::null_mut(),
msg_namelen: 0,
msg_iov: &mut iov,
msg_iovlen: 1,
msg_control: control.as_mut_ptr().cast(),
msg_controllen: control.len(),
msg_flags: 0,
};
let read = unsafe { libc::recvmsg(stream.as_raw_fd(), &mut header, 0) };
if read < 0 {
return Err(std::io::Error::last_os_error());
}
let mut fds = VecDeque::new();
let mut cmsg = unsafe { libc::CMSG_FIRSTHDR(&header) };
while !cmsg.is_null() {
let is_rights = unsafe {
(*cmsg).cmsg_level == libc::SOL_SOCKET && (*cmsg).cmsg_type == libc::SCM_RIGHTS
};
if is_rights {
let data_len = unsafe { (*cmsg).cmsg_len as usize }
.saturating_sub(mem::size_of::<libc::cmsghdr>());
let fd_count = data_len / mem::size_of::<RawFd>();
let data_ptr = unsafe { libc::CMSG_DATA(cmsg).cast::<RawFd>() };
for index in 0..fd_count {
fds.push_back(unsafe { *data_ptr.add(index) });
}
}
cmsg = unsafe { libc::CMSG_NXTHDR(&header, cmsg) };
}
Ok((read as usize, fds))
}
pub fn send_with_rights(
stream: &mut UnixStream,
object_id: u32,
opcode: u16,
payload: &[u8],
fds: &[RawFd],
) -> std::io::Result<()> {
let size = 8 + payload.len();
let mut msg = Vec::with_capacity(size);
push_u32(&mut msg, object_id);
push_u32(&mut msg, ((size as u32) << 16) | u32::from(opcode));
msg.extend_from_slice(payload);
if fds.is_empty() {
stream.write_all(&msg)?;
return Ok(());
}
let mut iov = libc::iovec {
iov_base: msg.as_mut_ptr().cast(),
iov_len: msg.len(),
};
let control_len =
unsafe { libc::CMSG_SPACE((fds.len() * mem::size_of::<RawFd>()) as u32) as usize };
let mut control = vec![0u8; control_len];
let header = libc::msghdr {
msg_name: std::ptr::null_mut(),
msg_namelen: 0,
msg_iov: &mut iov,
msg_iovlen: 1,
msg_control: control.as_mut_ptr().cast(),
msg_controllen: control.len(),
msg_flags: 0,
};
unsafe {
let cmsg = libc::CMSG_FIRSTHDR(&header);
if cmsg.is_null() {
return Err(std::io::Error::other(
"failed to allocate SCM_RIGHTS header",
));
}
(*cmsg).cmsg_level = libc::SOL_SOCKET;
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
(*cmsg).cmsg_len = libc::CMSG_LEN((fds.len() * mem::size_of::<RawFd>()) as u32) as _;
std::ptr::copy_nonoverlapping(
fds.as_ptr().cast::<u8>(),
libc::CMSG_DATA(cmsg).cast::<u8>(),
fds.len() * mem::size_of::<RawFd>(),
);
}
let written = unsafe { libc::sendmsg(stream.as_raw_fd(), &header, 0) };
if written < 0 {
return Err(std::io::Error::last_os_error());
}
if written as usize != msg.len() {
return Err(std::io::Error::other(format!(
"short sendmsg write: expected {}, got {}",
msg.len(),
written
)));
}
Ok(())
}