blue-recorder/src/wayland_record.rs
Ask

243 lines
8.4 KiB
Rust

{a829d64490f5c35a2c09243a454a407ef0f5fa73 true 8589 wayland_record.rs 0xc00302c4d0}

use gst::prelude::*;
use gstreamer as gst;
use std::collections::HashMap;
use zbus::{
dbus_proxy,
export::futures_util::TryStreamExt,
zvariant::{ObjectPath, OwnedObjectPath, Structure, Value},
Connection, MessageStream, MessageType, Result
};
#[derive(Clone, Copy)]
pub enum RecordTypes {
Default,
Monitor,
Window,
MonitorOrWindow,
}
#[derive(Clone, Copy)]
pub enum CursorModeTypes {
Default,
Hidden,
Show
}
#[dbus_proxy(
interface = "org.freedesktop.portal.ScreenCast",
default_service = "org.freedesktop.portal.Desktop",
default_path = "/org/freedesktop/portal/desktop"
)]
trait ScreenCast {
async fn create_session(&self, options: HashMap<&str, Value<'_>>) -> Result<OwnedObjectPath>;
async fn select_sources(
&self,
session_handle: ObjectPath<'_>,
options: HashMap<&str, Value<'_>>,
) -> Result<OwnedObjectPath>;
async fn start(
&self,
session_handle: ObjectPath<'_>,
parent_window: &str,
options: HashMap<&str, Value<'_>>,
) -> Result<OwnedObjectPath>;
}
#[derive(Clone)]
pub struct WaylandRecorder {
connection: Connection,
screen_cast_proxy: ScreenCastProxy<'static>,
session_path: String,
pipeline: Option<gst::Pipeline>,
filename: String,
}
impl WaylandRecorder {
pub async fn new() -> Self {
let connection = Connection::session().await.expect("failed to connect to session bus");
let screen_cast_proxy = ScreenCastProxy::new(&connection).await.expect("failed to create dbus proxy for screen-cast");
gst::init().expect("failed to initialize gstreamer");
WaylandRecorder {
connection,
screen_cast_proxy,
session_path: String::new(),
filename: String::from("blue_recorder.webm"),
pipeline: None,
}
}
pub async fn start(&mut self, filename: String, record_type: RecordTypes, cursor_mode_type: CursorModeTypes) -> bool {
self.screen_cast_proxy.create_session(HashMap::from([
("handle_token", Value::from("blue_recorder_1")),
("session_handle_token", Value::from("blue_recorder_1")),
]))
.await.expect("failed to create session");
let mut message_stream = MessageStream::from(self.connection.clone());
self.filename = filename.clone();
let mut first_empty_signal_called = false;
while let Some(msg) = message_stream.try_next().await.expect("failed to get message") {
match msg.message_type() {
MessageType::Signal => {
let (_, response) = msg.body::<(u32, HashMap<&str, Value>)>().expect("failed to get body");
if response.len() == 0 {
if first_empty_signal_called {
return false;
} else {
first_empty_signal_called = true;
continue;
}
}
if response.contains_key("session_handle") {
self.handle_session(
self.screen_cast_proxy.clone(),
response.clone(),
record_type,
cursor_mode_type
)
.await.expect("failed to handle session");
continue;
}
if response.contains_key("streams") {
self.record_screen_cast(response.clone()).await.expect("failed to record screen cast");
break;
}
}
_ => {
println!("\n\nUnkown message: {:?}", msg);
}
}
}
true
}
pub async fn stop(&mut self) {
if let Some(pipeline) = self.pipeline.clone() {
pipeline
.set_state(gst::State::Null)
.expect("failed to stop pipeline");
}
if self.session_path.len() > 0 {
println!("Closing session...: {:?}", self.session_path.replace("request", "session"));
self.connection.clone().call_method(Some("org.freedesktop.portal.Desktop"), self.session_path.clone().replace("request", "session"), Some("org.freedesktop.portal.Session"), "Close", &()).await.expect("failed to close session");
self.session_path = String::new();
}
}
async fn handle_session(
&mut self,
screen_cast_proxy: ScreenCastProxy<'_>,
response: HashMap<&str, Value<'_>>,
record_type: RecordTypes,
cursor_mode_type: CursorModeTypes
) -> Result<()> {
let response_session_handle = response
.get("session_handle")
.expect("cannot get session_handle")
.clone()
.downcast::<String>()
.expect("cannot down cast session_handle");
self.session_path = response_session_handle.clone();
screen_cast_proxy
.select_sources(
ObjectPath::try_from(response_session_handle.clone())?,
HashMap::from([
("handle_token", Value::from("blue_recorder_1")),
(
"types",
Value::from(match record_type {
RecordTypes::Monitor => 1u32,
RecordTypes::Window => 2u32,
RecordTypes::MonitorOrWindow => 3u32,
_ => 0u32,
}),
),
(
"cursor_mode",
Value::from(match cursor_mode_type {
CursorModeTypes::Hidden => 1u32,
CursorModeTypes::Show => 2u32,
_ => 0u32,
}),
),
]),
)
.await?;
screen_cast_proxy
.start(
ObjectPath::try_from(response_session_handle.clone())?,
"parent_window",
HashMap::from([("handle_token", Value::from("blue_recorder_1"))]),
)
.await?;
Ok(())
}
async fn record_screen_cast(&mut self, response: HashMap<&str, Value<'_>>) -> Result<()> {
let streams: &Value<'_> = response.get("streams").expect("cannot get streams");
// get fields from nested structure inside elements
// NOTICE: this is not the best way to get node_id, but it works for now
let stream_node_id: u32 = streams
.clone()
.downcast::<Vec<Value>>()
.expect("cannot down cast streams to vec array")
.get(0)
.expect("cannot get first object from streams array")
.clone()
.downcast::<Structure>()
.expect("cannot down cast first object to structure")
.fields()
.get(0)
.expect("cannot get first field from structure")
.clone()
.downcast::<u32>()
.expect("cannot down cast first field to u32");
// launch gstreamer pipeline
let gst_element: gst::Element = gst::parse_launch(&format!(
"pipewiresrc path={stream_node_id} ! videorate ! video/x-raw,framerate=30/1 ! videoconvert chroma-mode=none dither=none matrix-mode=output-only ! vp8enc max-quantizer=17 deadline=1 keyframe-mode=disabled buffer-size=20000 ! webmmux ! filesink location={filename}",
filename = self.filename
)).expect("failed to launch gstreamer pipeline");
// start pipeline
let pipeline: gst::Pipeline = gst_element
.dynamic_cast::<gst::Pipeline>()
.expect("pipeline error");
self.pipeline = Some(pipeline.clone());
pipeline
.set_state(gst::State::Playing)
.expect("failed to start pipeline");
println!("Recording Wayland screen cast...");
Ok(())
}
}
// #[async_std::main]
// async fn main() -> Result<()> {
// let mut recorder = WaylandRecorder::new().await;
// recorder.start(RecordTypes::Monitor, CursorModeTypes::Show).await?;
// // wait for user input to stop recording
// let mut input = String::new();
// std::io::stdin().read_line(&mut input).unwrap();
// recorder.stop();
// Ok(())
// }