diff --git a/src/capture/wayland.rs b/src/capture/wayland.rs index c34ad41..3e26732 100644 --- a/src/capture/wayland.rs +++ b/src/capture/wayland.rs @@ -27,7 +27,7 @@ impl WaylandCapture { let fd = proxy.open_pipe_wire_remote(&session, Default::default()).await?; - let stream = crate::capture::wayland_stream::PipeWireStream::new(node_id, fd.into_raw_fd())?; + let stream = crate::capture::wayland_stream::PipeWireStream::new(node_id, fd)?; Ok(Self { stream }) } diff --git a/src/capture/wayland_stream.rs b/src/capture/wayland_stream.rs index 3e1b644..3e52c31 100644 --- a/src/capture/wayland_stream.rs +++ b/src/capture/wayland_stream.rs @@ -1,30 +1,123 @@ -use std::os::unix::io::RawFd; +use pipewire as pw; +use pw::{properties::properties, spa}; +use spa::pod::Pod; +use std::os::unix::io::OwnedFd; use crate::capture::Frame; +use crossbeam_channel::{bounded, Sender, Receiver}; + +struct UserData { + format: spa::param::video::VideoInfoRaw, + tx: Sender, +} pub struct PipeWireStream { - receiver: crossbeam_channel::Receiver, + pub receiver: Receiver, } impl PipeWireStream { - pub fn new(_node_id: u32, _fd: RawFd) -> Result> { - let (tx, rx) = crossbeam_channel::bounded(5); + pub fn new(node_id: u32, fd: OwnedFd) -> Result> { + let (tx, rx) = bounded(5); std::thread::spawn(move || { - pipewire::init(); - loop { - let frame = Frame::DmaBuf { - width: 1920, - height: 1080, - format: 0, - fd: 42, - stride: 1920 * 4, - offset: 0, - }; - if tx.send(frame).is_err() { - break; + pw::init(); + let mainloop = pw::main_loop::MainLoopRc::new(None).unwrap(); + let context = pw::context::ContextRc::new(&mainloop, None).unwrap(); + + // Connect using the PipeWire FD provided by ashpd screencast portal + let core = match context.connect_fd_rc(fd, None) { + Ok(c) => c, + Err(_) => { + eprintln!("Failed to connect to pipewire via fd"); + return; } - std::thread::sleep(std::time::Duration::from_millis(16)); - } + }; + + let data = UserData { + format: Default::default(), + tx, + }; + + let stream = pw::stream::StreamBox::new( + &core, + "vrec-capture", + properties! { + *pw::keys::MEDIA_TYPE => "Video", + *pw::keys::MEDIA_CATEGORY => "Capture", + *pw::keys::MEDIA_ROLE => "Screen", + }, + ).unwrap(); + + let _listener = stream + .add_local_listener_with_user_data(data) + .param_changed(|_, user_data, id, param| { + let Some(param) = param else { return; }; + if id != pw::spa::param::ParamType::Format.as_raw() { return; } + + let (media_type, media_subtype) = match pw::spa::param::format_utils::parse_format(param) { + Ok(v) => v, + Err(_) => return, + }; + + if media_type != pw::spa::param::format::MediaType::Video || media_subtype != pw::spa::param::format::MediaSubtype::Raw { + return; + } + + user_data.format.parse(param).unwrap(); + println!("Wayland Stream Negotiated: {}x{}", user_data.format.size().width, user_data.format.size().height); + }) + .process(|stream, user_data| { + match stream.dequeue_buffer() { + None => {}, + Some(mut buffer) => { + let datas = buffer.datas_mut(); + if datas.is_empty() { return; } + let data = &mut datas[0]; + + // To properly handle DMA-BUFs, we would read the spa_data chunk and extract the fd + // For this MVP skeleton, we will push a mock DmaBuf to satisfy the pipeline + let frame = Frame::DmaBuf { + width: user_data.format.size().width as u32, + height: user_data.format.size().height as u32, + format: 0, + fd: data.fd() as i32, + stride: data.chunk().stride() as u32, + offset: data.chunk().offset() as u32, + }; + let _ = user_data.tx.try_send(frame); + } + } + }) + .register().unwrap(); + + let obj = pw::spa::pod::object!( + pw::spa::utils::SpaTypes::ObjectParamFormat, + pw::spa::param::ParamType::EnumFormat, + pw::spa::pod::property!( + pw::spa::param::format::FormatProperties::MediaType, + Id, + pw::spa::param::format::MediaType::Video + ), + pw::spa::pod::property!( + pw::spa::param::format::FormatProperties::MediaSubtype, + Id, + pw::spa::param::format::MediaSubtype::Raw + ), + ); + + let values: Vec = pw::spa::pod::serialize::PodSerializer::serialize( + std::io::Cursor::new(Vec::new()), + &pw::spa::pod::Value::Object(obj), + ).unwrap().0.into_inner(); + let mut params = [Pod::from_bytes(&values).unwrap()]; + + stream.connect( + spa::utils::Direction::Input, + Some(node_id), + pw::stream::StreamFlags::AUTOCONNECT, + &mut params, + ).unwrap(); + + mainloop.run(); }); Ok(Self { receiver: rx })