gd-wireportal-full/rust/src/portals/screencast.rs
2025-03-19 15:15:38 +01:00

151 lines
5.2 KiB
Rust

use ashpd::desktop::{
screencast::{CursorMode, Screencast, SourceType},
PersistMode,
};
use gdext_coroutines::prelude::*;
use godot::prelude::*;
use godot_tokio::AsyncRuntime;
use smol::{channel, lock::Mutex};
use std::sync::Arc;
#[derive(GodotClass)]
#[class(base = Node)]
/// Runner node for requesting a pipewire screencast
struct ScreencastRunner {
base: Base<Node>,
runner: Arc<Mutex<Runner>>,
receiver: channel::Receiver<Result<u32, String>>,
sender: channel::Sender<Result<u32, String>>,
last_result: Option<Result<u32, String>>,
}
struct Runner;
#[godot_api]
impl INode for ScreencastRunner {
fn init(base: Base<Node>) -> Self {
#[cfg(not(target_os = "linux"))]
panic!("Not supported on Windows");
let (sender, receiver) = channel::unbounded();
Self {
base,
receiver,
sender,
runner: Arc::new(Mutex::new(Runner {})),
last_result: None,
}
}
fn process(&mut self, _delta: f64) {
let channel_elements = self.receiver.len();
for _ in 0..channel_elements {
let result = self.receiver.recv();
let result = AsyncRuntime::block_on(result)
.expect("Failed to receive screencast request result");
self.last_result = Some(result.clone());
match result {
Ok(pipewire_node_id) => self
.base_mut()
.emit_signal("screencast_sucess", &[pipewire_node_id.to_variant()]),
Err(reason) => self
.base_mut()
.emit_signal("screencast_failure", &[reason.to_variant()]),
};
}
}
}
#[godot_api]
impl ScreencastRunner {
/// Emited after a request to capture a window, if that request is successful.
/// Contains the id of the pipewire node with the stream
#[signal]
fn screencast_sucess(pipewire_node_id: u32);
/// Emited after a request to capture a window, if that request has failed.
/// Contains the reason (as string) for why the request failed
#[signal]
fn screencast_failure(reason: String);
/// Emited after a request to capture a window completes
/// pipe_wire_node_id will contain the id of the pipewire node with the stream if ok is true
/// reason will contain the reason (as String) why the request failed if ok is false
/// ok indicates whether the request was successful or not
#[signal]
fn screencast_result(pipewire_node_id: u32, reason: String, ok: bool);
/// Request a new window/screen capture from the user.
/// Returns the async runner node.
/// You can either await the `completed` result on that node
#[func]
fn request_capture(&mut self) -> Gd<SpireCoroutine> {
let sender = self.sender.clone();
let binding = self.runner.clone();
// This is cursed. First having to spawn the task on the background runtime
let x = AsyncRuntime::spawn(async move {
let runner = binding.lock().await;
runner.run_capture(sender).await;
});
// And then using the engine's runtime to await the completion in the background
self.start_async_task(async move {
let _ = x.await;
})
}
}
impl Runner {
async fn run_capture(&self, result_chan: smol::channel::Sender<Result<u32, String>>) {
let proxy = Screencast::new().await.expect("Screencast proxy failed");
let session = match proxy.create_session().await {
Ok(x) => x,
Err(err) => {
godot_warn!("Failed to create session: {}", err);
return;
}
};
match proxy
.select_sources(
&session,
CursorMode::Metadata,
SourceType::Monitor | SourceType::Window,
true,
None,
PersistMode::DoNot,
)
.await
{
Ok(_) => {}
Err(err) => {
godot_warn!("Err while selecting source: {}", err);
return;
}
}
let starter = match proxy.start(&session, None).await {
Ok(x) => x,
Err(err) => {
godot_warn!("Error while starting: {}", err);
return;
}
};
let result = match starter.response() {
Ok(x) => x,
Err(err) => {
godot_warn!("Err for response: {}", err);
return;
}
};
godot_print!("Got response");
// This is ugly. But I can't await inside for_each
// so instead just collect all futures and await them in a manual loop
let things: Vec<_> = result
.streams()
.iter()
.map(|stream| {
godot_print!("node id: {}", stream.pipe_wire_node_id());
godot_print!("size: {:?}", stream.size());
godot_print!("position: {:?}", stream.position());
result_chan.send(Ok(stream.pipe_wire_node_id()))
})
.collect();
for future in things {
let _ = future.await;
}
}
}