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 })