Compare commits

..

No commits in common. "da436d5814e93eaa787e4ab56cd101ebeff5e06a" and "1be08b1b63e8b4f9c3899199f7d12064ee880ad3" have entirely different histories.

10 changed files with 177 additions and 391 deletions

View file

@ -2,7 +2,7 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://b2u3ggnem2sce"
uid="uid://cnyljy7e3rf70"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false

View file

@ -3,14 +3,8 @@ extends Node2D
func _on_button_pressed() -> void:
print("Starting request in godot")
await $ScreencastRunner.request_capture()
print("awaiting result")
await $ScreencastRunner.screencast_result
$XdgPortals.request_screencast()
func _on_screencast_runner_screencast_failure(reason: String) -> void:
push_warning("Screencast failed, ", reason)
func _on_screencast_runner_screencast_sucess(pipewire_node_id: int) -> void:
print("Screencast success: ", pipewire_node_id)
func _on_xdg_portals_screencast_sucess(pipewire_node_id: int) -> void:
print("Stream id in godot: ", pipewire_node_id)

View file

@ -10,8 +10,7 @@ offset_right = 8.0
offset_bottom = 8.0
text = "Request"
[node name="ScreencastRunner" type="ScreencastRunner" parent="."]
[node name="XdgPortals" type="XdgPortals" parent="."]
[connection signal="pressed" from="Button" to="." method="_on_button_pressed"]
[connection signal="screencast_failure" from="ScreencastRunner" to="." method="_on_screencast_runner_screencast_failure"]
[connection signal="screencast_sucess" from="ScreencastRunner" to="." method="_on_screencast_runner_screencast_sucess"]
[connection signal="screencast_sucess" from="XdgPortals" to="." method="_on_xdg_portals_screencast_sucess"]

213
rust/Cargo.lock generated
View file

@ -71,102 +71,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "async-channel"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-executor"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"slab",
]
[[package]]
name = "async-fs"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
dependencies = [
"async-lock",
"blocking",
"futures-lite",
]
[[package]]
name = "async-io"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059"
dependencies = [
"async-lock",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite",
"parking",
"polling",
"rustix 0.38.44",
"slab",
"tracing",
"windows-sys 0.59.0",
]
[[package]]
name = "async-lock"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
"event-listener",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-net"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
dependencies = [
"async-io",
"blocking",
"futures-lite",
]
[[package]]
name = "async-process"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb"
dependencies = [
"async-channel",
"async-io",
"async-lock",
"async-signal",
"async-task",
"blocking",
"cfg-if",
"event-listener",
"futures-lite",
"rustix 0.38.44",
"tracing",
]
[[package]]
name = "async-recursion"
version = "1.1.1"
@ -178,30 +82,6 @@ dependencies = [
"syn",
]
[[package]]
name = "async-signal"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3"
dependencies = [
"async-io",
"async-lock",
"atomic-waker",
"cfg-if",
"futures-core",
"futures-io",
"rustix 0.38.44",
"signal-hook-registry",
"slab",
"windows-sys 0.59.0",
]
[[package]]
name = "async-task"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-trait"
version = "0.1.87"
@ -213,12 +93,6 @@ dependencies = [
"syn",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.4.0"
@ -267,19 +141,6 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "blocking"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
dependencies = [
"async-channel",
"async-task",
"futures-io",
"futures-lite",
"piper",
]
[[package]]
name = "bytes"
version = "1.10.1"
@ -577,7 +438,6 @@ dependencies = [
"godot",
"godot_tokio",
"pipewire",
"smol",
]
[[package]]
@ -587,7 +447,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92b254068dbdb5a5a9d702039f5b741ac9a454617c28f69c5cc6c31792c7d11d"
dependencies = [
"godot",
"smol",
]
[[package]]
@ -737,12 +596,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "hex"
version = "0.4.3"
@ -963,12 +816,6 @@ dependencies = [
"system-deps",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.9.2"
@ -1126,17 +973,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
dependencies = [
"atomic-waker",
"fastrand",
"futures-io",
]
[[package]]
name = "pipewire"
version = "0.8.0"
@ -1171,21 +1007,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "polling"
version = "3.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix 0.38.44",
"tracing",
"windows-sys 0.59.0",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@ -1293,19 +1114,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.0.2"
@ -1315,7 +1123,7 @@ dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.9.2",
"linux-raw-sys",
"windows-sys 0.59.0",
]
@ -1389,23 +1197,6 @@ version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "smol"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f"
dependencies = [
"async-channel",
"async-executor",
"async-fs",
"async-io",
"async-lock",
"async-net",
"async-process",
"blocking",
"futures-lite",
]
[[package]]
name = "socket2"
version = "0.5.8"
@ -1479,7 +1270,7 @@ dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix 1.0.2",
"rustix",
"windows-sys 0.59.0",
]

View file

@ -5,12 +5,10 @@ edition = "2021"
[dependencies]
ashpd = "0.11.0"
gdext_coroutines = {version="0.7.1", features=["async"]}
# godot = {version="0.2.4", features=["api-4-2", "experimental-godot-api", "experimental-threads"]}
gdext_coroutines = "0.7.1"
godot = "0.2.4"
godot_tokio = "0.3.0"
pipewire = "0.8.0"
smol = "2.0.2"
[lib]
crate-type = ["cdylib"]

View file

@ -2,14 +2,14 @@
#![feature(coroutine_trait)]
#![feature(stmt_expr_attributes)]
#![feature(unboxed_closures)]
//#![cfg_attr(feature = "async", feature(async_fn_traits))]
#![cfg_attr(feature = "async", feature(async_fn_traits))]
use godot::{classes::Engine, prelude::*};
use godot_tokio::AsyncRuntime;
mod pipewire;
mod player;
mod portals;
//mod screencapture;
struct Extension;

View file

@ -1,28 +0,0 @@
use std::thread;
use godot::prelude::*;
#[derive(GodotClass)]
#[class(base=Node)]
/// Node for interacting with Pipewire
/// Intended to not be spawned manually, as the plugin will create a
struct Pipewire {
base: Base<Node>,
/// Handle for the thread running the pipewire main loop
thread_handle: Option<thread::Thread>,
}
#[godot_api]
impl INode for Pipewire {
fn init(base: Base<Node>) -> Self {
Self {
base,
thread_handle: None,
}
}
}
#[godot_api]
impl Pipewire {
pub const SINGLETON: &'static str = "Pipewire";
}

View file

@ -3,14 +3,14 @@ use godot::prelude::*;
mod screencast;
#[derive(GodotClass)]
#[class(base=Node)]
#[class(base=Object)]
struct XdgPortals {
base: Base<Node>,
base: Base<Object>,
}
#[godot_api]
impl INode for XdgPortals {
fn init(base: Base<Node>) -> Self {
impl IObject for XdgPortals {
fn init(base: Base<Object>) -> Self {
Self { base }
}
}

View file

@ -2,148 +2,83 @@ 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;
//use std::thread;
#[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>>,
#[class(base=RefCounted)]
struct ScreencastResult {
base: Base<RefCounted>,
}
struct Runner;
#[godot_api]
impl IRefCounted for ScreencastResult {
fn init(base: Base<RefCounted>) -> Self {
let mut result = Self { base };
//thread::spawn(|| result.capture_runner());
result
}
}
#[godot_api]
impl INode for ScreencastRunner {
fn init(base: Base<Node>) -> Self {
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()]),
impl ScreencastResult {
fn capture_runner(&mut self) {
AsyncRuntime::block_on(async {
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;
}
}
#[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| {
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");
result.streams().iter().for_each(|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;
}
self.base_mut().emit_signal(
"screencast_sucess",
&[stream.pipe_wire_node_id().to_variant()],
);
});
});
}
}
unsafe impl Send for ScreencastResult {}
unsafe impl Sync for ScreencastResult {}

97
rust/src/screencapture.rs Normal file
View file

@ -0,0 +1,97 @@
use ashpd::desktop::{
screencast::{CursorMode, Screencast, SourceType},
PersistMode,
};
use godot::classes::{INode, Node};
use godot::prelude::*;
use godot_tokio::AsyncRuntime;
#[derive(GodotClass)]
#[class(base=Node)]
struct XdgPortals {
base: Base<Node>,
last_request_completed: bool,
}
#[godot_api]
impl INode for XdgPortals {
fn init(base: Base<Node>) -> Self {
Self {
base,
last_request_completed: true,
}
}
}
#[godot_api]
impl XdgPortals {
#[signal]
fn screencast_sucess(pipewire_node_id: u32);
#[func]
fn was_last_request_completed(&self) -> bool {
self.last_request_completed
}
#[func]
fn request_screencast(&mut self) {
godot_print!("Starting request");
self.last_request_completed = false;
AsyncRuntime::block_on(async {
godot_print!("Starting request async");
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;
}
};
godot_print!("Got proxy");
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;
}
}
godot_print!("Selected source");
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");
result.streams().iter().for_each(|stream| {
godot_print!("node id: {}", stream.pipe_wire_node_id());
godot_print!("size: {:?}", stream.size());
godot_print!("position: {:?}", stream.position());
self.base_mut().emit_signal(
"screencast_sucess",
&[stream.pipe_wire_node_id().to_variant()],
);
self.last_request_completed = true;
});
});
}
}