151 lines
5.2 KiB
Rust
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;
|
|
}
|
|
}
|
|
}
|