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, runner: Arc>, receiver: channel::Receiver>, sender: channel::Sender>, last_result: Option>, } struct Runner; #[godot_api] impl INode for ScreencastRunner { fn init(base: Base) -> 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 { 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>) { 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; } } }