Skip to content

Instantly share code, notes, and snippets.

@SyedAhkam
Created November 17, 2024 14:21
Show Gist options
  • Save SyedAhkam/5735ebc0f88ae7e82f0c6f3a3bfbf9ad to your computer and use it in GitHub Desktop.
Save SyedAhkam/5735ebc0f88ae7e82f0c6f3a3bfbf9ad to your computer and use it in GitHub Desktop.
use std::{
fs::File,
io::Cursor,
os::fd::{FromRawFd, IntoRawFd, OwnedFd},
sync::{Arc, Mutex},
};
use ashpd::desktop::{
screencast::{CursorMode, Screencast, SourceType, Stream as AshStream},
PersistMode,
};
use pipewire::{
context::Context,
keys::{MEDIA_CATEGORY, MEDIA_ROLE, MEDIA_TYPE},
main_loop::MainLoop,
properties::properties,
spa::{
param::{
format::{FormatProperties, MediaSubtype, MediaType},
video::{VideoFormat, VideoInfoRaw},
ParamType,
},
pod::{self, deserialize::FractionVisitor, serialize::PodSerializer, Pod},
sys::{
spa_buffer, spa_meta_header, SPA_META_Header, SPA_PARAM_META_size, SPA_PARAM_META_type,
},
utils::{Direction, Fraction, Id, Rectangle, SpaTypes},
},
stream::{Stream, StreamFlags},
};
async fn open_portal() -> ashpd::Result<(AshStream, OwnedFd)> {
let proxy = Screencast::new().await?;
let session = proxy.create_session().await?;
proxy
.select_sources(
&session,
CursorMode::Embedded,
SourceType::Monitor.into(),
false,
None,
PersistMode::DoNot,
)
.await?;
let response = proxy.start(&session, None).await?.response()?;
let stream = response.streams().first().unwrap().to_owned();
let fd = proxy.open_pipe_wire_remote(&session).await?;
Ok((stream, fd))
}
async fn start_streaming(node_id: u32, fd: OwnedFd) {
pipewire::init();
let main_loop = MainLoop::new(None).expect("Failed to create main loop");
let context = Context::new(&main_loop).expect("Failed to create PipeWire context");
let core = context
.connect_fd(fd, None)
.expect("Failed to connect to PipeWire");
let stream = Stream::new(
&core,
"ScreenCast",
properties! {
*MEDIA_TYPE => "Video",
*MEDIA_CATEGORY => "Capture",
*MEDIA_ROLE => "Screen",
},
)
.expect("Failed to create stream");
stream
.add_local_listener::<Vec<String>>()
.state_changed(|_, _, _, _| {
println!("Stream state changed");
})
.process(|stream, _| {
println!("{:?}", stream.state());
})
.register()
.expect("Failed to connect local listener");
let obj = pod::object!(
SpaTypes::ObjectParamFormat,
ParamType::EnumFormat,
pod::property!(FormatProperties::MediaType, Id, MediaType::Video),
pod::property!(FormatProperties::MediaSubtype, Id, MediaSubtype::Raw),
pod::property!(
FormatProperties::VideoFormat,
Choice,
Enum,
Id,
VideoFormat::RGB,
VideoFormat::RGBA,
// VideoFormat::RGBx,
// VideoFormat::BGRx,
),
pod::property!(
FormatProperties::VideoSize,
Choice,
Range,
Rectangle,
Rectangle {
// Default
width: 2560,
height: 1600,
},
Rectangle {
// Min
width: 1,
height: 1,
},
Rectangle {
// Max
width: 4096,
height: 4096,
}
),
pod::property!(
FormatProperties::VideoFramerate,
Choice,
Range,
Fraction,
Fraction { num: 30, denom: 1 },
Fraction { num: 1, denom: 1 },
Fraction {
num: 1000,
denom: 1
}
),
);
let metas_obj = pod::object!(
SpaTypes::ObjectParamMeta,
ParamType::Meta,
pod::Property::new(SPA_PARAM_META_type, pod::Value::Id(Id(SPA_META_Header))),
pod::Property::new(
SPA_PARAM_META_size,
pod::Value::Int(size_of::<spa_meta_header>() as i32)
),
);
let values: Vec<u8> =
PodSerializer::serialize(Cursor::new(Vec::new()), &pod::Value::Object(obj))
.unwrap()
.0
.into_inner();
let metas_values: Vec<u8> = PodSerializer::serialize(
std::io::Cursor::new(Vec::new()),
&pod::Value::Object(metas_obj),
)
.unwrap()
.0
.into_inner();
println!("Node ID: {:?}", node_id);
let params = &mut [
Pod::from_bytes(&values).unwrap(),
Pod::from_bytes(&metas_values).unwrap(),
];
stream
.connect(
Direction::Output,
Some(node_id),
StreamFlags::AUTOCONNECT | StreamFlags::MAP_BUFFERS,
params,
)
.expect("Failed to connect stream");
main_loop.run();
}
smol_macros::main! {
async fn main() {
let (stream, fd) = open_portal().await.expect("failed to open portal");
start_streaming(stream.pipe_wire_node_id(), fd).await;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment