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:
@@ -0,0 +1,94 @@
|
||||
//! The audio daemon for RedoxOS.
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::addr_of_mut;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{mem, process, slice, thread};
|
||||
|
||||
use anyhow::Context;
|
||||
use ioslice::IoSlice;
|
||||
use libredox::flag;
|
||||
use libredox::{error::Result, Fd};
|
||||
|
||||
use redox_scheme::Socket;
|
||||
use scheme_utils::ReadinessBased;
|
||||
|
||||
use daemon::SchemeDaemon;
|
||||
|
||||
use self::scheme::AudioScheme;
|
||||
|
||||
mod scheme;
|
||||
|
||||
extern "C" fn sigusr_handler(_sig: usize) {}
|
||||
|
||||
fn thread(scheme: Arc<Mutex<AudioScheme>>, pid: usize, hw_file: Fd) -> Result<()> {
|
||||
loop {
|
||||
let buffer = scheme.lock().unwrap().buffer();
|
||||
let buffer_u8 = unsafe {
|
||||
slice::from_raw_parts(buffer.as_ptr() as *const u8, mem::size_of_val(&buffer))
|
||||
};
|
||||
|
||||
// Wake up the scheme thread
|
||||
libredox::call::kill(pid, libredox::flag::SIGUSR1 as u32)?;
|
||||
|
||||
hw_file.write(&buffer_u8)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> {
|
||||
// Handle signals from the hw thread
|
||||
|
||||
let new_sigaction = unsafe {
|
||||
let mut sigaction = MaybeUninit::<libc::sigaction>::uninit();
|
||||
addr_of_mut!((*sigaction.as_mut_ptr()).sa_flags).write(0);
|
||||
libc::sigemptyset(addr_of_mut!((*sigaction.as_mut_ptr()).sa_mask));
|
||||
addr_of_mut!((*sigaction.as_mut_ptr()).sa_sigaction).write(sigusr_handler as usize);
|
||||
sigaction.assume_init()
|
||||
};
|
||||
libredox::call::sigaction(flag::SIGUSR1, Some(&new_sigaction), None)?;
|
||||
|
||||
let pid = libredox::call::getpid()?;
|
||||
|
||||
let hw_file = Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0)?;
|
||||
|
||||
let socket = Socket::create().context("failed to create scheme")?;
|
||||
|
||||
let scheme = Arc::new(Mutex::new(AudioScheme::new()));
|
||||
|
||||
let _ = daemon.ready_sync_scheme(&socket, &mut *scheme.lock().unwrap());
|
||||
|
||||
// Enter a constrained namespace
|
||||
let ns = libredox::call::mkns(&[
|
||||
IoSlice::new(b"memory"),
|
||||
IoSlice::new(b"rand"), // for HashMap
|
||||
])
|
||||
.context("failed to make namespace")?;
|
||||
libredox::call::setns(ns).context("failed to set namespace")?;
|
||||
|
||||
// Spawn a thread to mix and send audio data
|
||||
let scheme_thread = scheme.clone();
|
||||
let _thread = thread::spawn(move || thread(scheme_thread, pid, hw_file));
|
||||
|
||||
let mut readiness = ReadinessBased::new(&socket, 16);
|
||||
|
||||
loop {
|
||||
readiness.read_and_process_requests(&mut *scheme.lock().unwrap())?;
|
||||
readiness.poll_all_requests(&mut *scheme.lock().unwrap())?;
|
||||
readiness.write_responses()?;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
SchemeDaemon::new(inner);
|
||||
}
|
||||
|
||||
fn inner(x: SchemeDaemon) -> ! {
|
||||
match daemon(x) {
|
||||
Ok(()) => {
|
||||
process::exit(0);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("audiod: {}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
use redox_scheme::{CallerCtx, OpenResult};
|
||||
use scheme_utils::HandleMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::str;
|
||||
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, ENOENT, EWOULDBLOCK};
|
||||
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
|
||||
// The strict buffer size of the audiohw: driver
|
||||
const HW_BUFFER_SIZE: usize = 512;
|
||||
// The desired buffer size of each handle
|
||||
const HANDLE_BUFFER_SIZE: usize = 4096;
|
||||
|
||||
enum Handle {
|
||||
Audio { buffer: VecDeque<(i16, i16)> },
|
||||
// TODO: move volume to audiohw:?
|
||||
// TODO: Use SYS_CALL to handle this better?
|
||||
Volume,
|
||||
SchemeRoot,
|
||||
}
|
||||
|
||||
pub struct AudioScheme {
|
||||
handles: HandleMap<Handle>,
|
||||
volume: i32,
|
||||
}
|
||||
|
||||
impl AudioScheme {
|
||||
pub fn new() -> Self {
|
||||
AudioScheme {
|
||||
handles: HandleMap::new(),
|
||||
volume: 50,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer(&mut self) -> [(i16, i16); HW_BUFFER_SIZE] {
|
||||
let mut mix_buffer = [(0i16, 0i16); HW_BUFFER_SIZE];
|
||||
|
||||
// Multiply each sample by the cube of volume divided by 100
|
||||
// This mimics natural perception of loudness
|
||||
let volume_factor = ((self.volume as f32) / 100.0).powi(3);
|
||||
for (_id, handle) in self.handles.iter_mut() {
|
||||
match handle {
|
||||
Handle::Audio { ref mut buffer } => {
|
||||
let mut i = 0;
|
||||
while i < mix_buffer.len() {
|
||||
if let Some(sample) = buffer.pop_front() {
|
||||
let left = (sample.0 as f32 * volume_factor) as i16;
|
||||
let right = (sample.1 as f32 * volume_factor) as i16;
|
||||
mix_buffer[i].0 = mix_buffer[i].0.saturating_add(left);
|
||||
mix_buffer[i].1 = mix_buffer[i].1.saturating_add(right);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
mix_buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for AudioScheme {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(self.handles.insert(Handle::SchemeRoot))
|
||||
}
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
_flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) {
|
||||
return Err(Error::new(EACCES));
|
||||
}
|
||||
|
||||
let (handle, flags) = match path.trim_matches('/') {
|
||||
"" => (
|
||||
Handle::Audio {
|
||||
buffer: VecDeque::new(),
|
||||
},
|
||||
NewFdFlags::empty(),
|
||||
),
|
||||
"volume" => (Handle::Volume, NewFdFlags::POSITIONED),
|
||||
_ => return Err(Error::new(ENOENT)),
|
||||
};
|
||||
|
||||
let id = self.handles.insert(handle);
|
||||
|
||||
Ok(OpenResult::ThisScheme { number: id, flags })
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &mut [u8],
|
||||
off: u64,
|
||||
_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
//TODO: check flags for readable
|
||||
match self.handles.get_mut(id)? {
|
||||
Handle::Audio { buffer: _ } => {
|
||||
//TODO: audio input?
|
||||
Err(Error::new(EBADF))
|
||||
}
|
||||
Handle::Volume => {
|
||||
let Ok(off) = usize::try_from(off) else {
|
||||
return Ok(0);
|
||||
};
|
||||
//TODO: should we allocate every time?
|
||||
let bytes = format!("{}", self.volume).into_bytes();
|
||||
let src = bytes.get(off..).unwrap_or(&[]);
|
||||
let len = src.len().min(buf.len());
|
||||
buf[..len].copy_from_slice(&src[..len]);
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &[u8],
|
||||
offset: u64,
|
||||
_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
//TODO: check flags for writable
|
||||
match self.handles.get_mut(id)? {
|
||||
Handle::Audio { ref mut buffer } => {
|
||||
if buffer.len() >= HANDLE_BUFFER_SIZE {
|
||||
Err(Error::new(EWOULDBLOCK))
|
||||
} else {
|
||||
let mut i = 0;
|
||||
while i + 4 <= buf.len() {
|
||||
buffer.push_back((
|
||||
(buf[i] as i16) | ((buf[i + 1] as i16) << 8),
|
||||
(buf[i + 2] as i16) | ((buf[i + 3] as i16) << 8),
|
||||
));
|
||||
|
||||
i += 4;
|
||||
}
|
||||
|
||||
Ok(i)
|
||||
}
|
||||
}
|
||||
Handle::Volume => {
|
||||
//TODO: support other offsets?
|
||||
if offset == 0 {
|
||||
let value = str::from_utf8(buf)
|
||||
.map_err(|_| Error::new(EINVAL))?
|
||||
.trim()
|
||||
.parse::<i32>()
|
||||
.map_err(|_| Error::new(EINVAL))?;
|
||||
if value >= 0 && value <= 100 {
|
||||
self.volume = value;
|
||||
Ok(buf.len())
|
||||
} else {
|
||||
Err(Error::new(EINVAL))
|
||||
}
|
||||
} else {
|
||||
// EOF
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user