diff --git a/rust/src/lib.rs b/rust/src/lib.rs index c93a3e5..ca65c04 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -6,6 +6,7 @@ use godot::{classes::Engine, prelude::*}; use godot_tokio::AsyncRuntime; +use pipewire::Pipewire; mod pipewire; mod player; @@ -16,11 +17,14 @@ struct Extension; #[gdextension] unsafe impl ExtensionLibrary for Extension { fn on_level_init(level: InitLevel) { + #[cfg(not(target_os = "linux"))] + panic!("Not supported on Windows"); if level == InitLevel::Scene { let mut engine = Engine::singleton(); // This is where we register our async runtime singleton. engine.register_singleton(AsyncRuntime::SINGLETON, &AsyncRuntime::new_alloc()); + engine.register_singleton(Pipewire::SINGLETON, &Pipewire::new_alloc()); } } fn on_level_deinit(level: InitLevel) { @@ -28,15 +32,19 @@ unsafe impl ExtensionLibrary for Extension { let mut engine = Engine::singleton(); // Here is where we free our async runtime singleton from memory. - if let Some(async_singleton) = engine.get_singleton(AsyncRuntime::SINGLETON) { - engine.unregister_singleton(AsyncRuntime::SINGLETON); - async_singleton.free(); - } else { - godot_warn!( - "Failed to find & free singleton -> {}", - AsyncRuntime::SINGLETON - ); - } + Extension::free_singleton(&mut engine, AsyncRuntime::SINGLETON); + Extension::free_singleton(&mut engine, Pipewire::SINGLETON); + } + } +} + +impl Extension { + fn free_singleton(engine: &mut Engine, name: &str) { + if let Some(async_singleton) = engine.get_singleton(name) { + engine.unregister_singleton(name); + async_singleton.free(); + } else { + godot_warn!("Failed to find & free singleton -> {}", name); } } } diff --git a/rust/src/pipewire/mod.rs b/rust/src/pipewire/mod.rs index 2a59726..336cf86 100644 --- a/rust/src/pipewire/mod.rs +++ b/rust/src/pipewire/mod.rs @@ -1,23 +1,46 @@ use std::thread; -use godot::prelude::*; +use godot::{classes::notify::NodeNotification, prelude::*}; +use pipewire::{ + channel::{Receiver, Sender}, + main_loop::MainLoop, +}; #[derive(GodotClass)] #[class(base=Node)] /// Node for interacting with Pipewire /// Intended to not be spawned manually, as the plugin will create a -struct Pipewire { +pub(crate) struct Pipewire { base: Base, + /// Handle for the thread running the pipewire main loop - thread_handle: Option, + thread_handle: Option>, + shutdown_sender: Sender, } +struct Terminate; + #[godot_api] impl INode for Pipewire { fn init(base: Base) -> Self { + let (thread, shutdown_sender) = Self::start_pipewire_thread(); Self { base, - thread_handle: None, + thread_handle: Some(thread), + shutdown_sender, + } + } + fn on_notification(&mut self, what: NodeNotification) { + if what == NodeNotification::PREDELETE + || what == NodeNotification::UNPARENTED + || what == NodeNotification::WM_CLOSE_REQUEST + || what == NodeNotification::EXIT_TREE + { + godot_print!("Sending shutdown signal to pipewire thread"); + let _ = self.shutdown_sender.send(Terminate {}); + let handle = self.thread_handle.take().unwrap(); + let _join_result = handle.join(); + godot_print!("Pipewire thread completed"); } } } @@ -25,4 +48,26 @@ impl INode for Pipewire { #[godot_api] impl Pipewire { pub const SINGLETON: &'static str = "Pipewire"; + + fn start_pipewire_thread() -> (thread::JoinHandle<()>, Sender) { + godot_print!("Starting pipewire thread"); + let (shutdown_sender, shutdown_receiver) = pipewire::channel::channel::(); + let thread_handle = thread::spawn(|| Self::pipewire_main(shutdown_receiver)); + (thread_handle, shutdown_sender) + } + + /// Func that actually runs the main loop in the thread + fn pipewire_main(shutdown_receiver: Receiver) { + let mainloop = MainLoop::new(None).expect("Failed to create pipewire main loop"); + + let _receiver = shutdown_receiver.attach(mainloop.loop_(), { + godot_print!("Stopping pipewire thread"); + let mainloop = mainloop.clone(); + move |_| mainloop.quit() + }); + + godot_print!("Entering pipewire main loop"); + mainloop.run(); + println!("Pipewire main loop exited"); + } }