diff --git a/src/niri.rs b/src/niri.rs index 8ac852702..78b1c3d6f 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -1,4 +1,4 @@ -use std::cell::{Cell, LazyCell, OnceCell, RefCell}; +use std::cell::{Cell, OnceCell, RefCell}; use std::collections::{HashMap, HashSet}; use std::ffi::OsString; use std::path::PathBuf; @@ -345,7 +345,9 @@ pub struct Niri { // Casts are dropped before PipeWire to prevent a double-free (yay). pub casts: Vec, - pub pipewire: LazyCell, Box Option>>, + pub pipewire: Option, + #[cfg(feature = "xdp-gnome-screencast")] + pub pw_to_niri: calloop::channel::Sender, // Screencast output for each mapped window. #[cfg(feature = "xdp-gnome-screencast")] @@ -1504,6 +1506,16 @@ impl State { }); } }, + PwToNiri::FatalError => { + warn!("stopping PipeWire due to fatal error"); + if let Some(pw) = self.niri.pipewire.take() { + let ids: Vec<_> = self.niri.casts.iter().map(|cast| cast.session_id).collect(); + for id in ids { + self.niri.stop_cast(id); + } + self.niri.event_loop.remove(pw.token); + } + } } } @@ -1533,10 +1545,19 @@ impl State { } }; - let Some(pw) = &*self.niri.pipewire else { - warn!("error starting screencast: PipeWire failed to initialize"); - self.niri.stop_cast(session_id); - return; + let pw = if let Some(pw) = &self.niri.pipewire { + pw + } else { + match PipeWire::new(&self.niri.event_loop, self.niri.pw_to_niri.clone()) { + Ok(pipewire) => self.niri.pipewire.insert(pipewire), + Err(err) => { + warn!( + "error starting screencast: PipeWire failed to initialize: {err:?}" + ); + self.niri.stop_cast(session_id); + return; + } + } }; let (target, size, refresh, alpha) = match target { @@ -1911,14 +1932,17 @@ impl Niri { } }; - let loop_handle = event_loop.clone(); - let pipewire = LazyCell::new(Box::new(move || match PipeWire::new(&loop_handle) { - Ok(pipewire) => Some(pipewire), - Err(err) => { - warn!("error connecting to PipeWire, screencasting will not work: {err:?}"); - None - } - }) as _); + #[cfg(feature = "xdp-gnome-screencast")] + let pw_to_niri = { + let (pw_to_niri, from_pipewire) = calloop::channel::channel(); + event_loop + .insert_source(from_pipewire, move |event, _, state| match event { + calloop::channel::Event::Msg(msg) => state.on_pw_msg(msg), + calloop::channel::Event::Closed => (), + }) + .unwrap(); + pw_to_niri + }; let display_source = Generic::new(display, Interest::READ, Mode::Level); event_loop @@ -2061,8 +2085,10 @@ impl Niri { ipc_server, ipc_outputs_changed: false, - pipewire, + pipewire: None, casts: vec![], + #[cfg(feature = "xdp-gnome-screencast")] + pw_to_niri, #[cfg(feature = "xdp-gnome-screencast")] mapped_cast_output: HashMap::new(), diff --git a/src/pw_utils.rs b/src/pw_utils.rs index 6712eeb3a..38262ff0f 100644 --- a/src/pw_utils.rs +++ b/src/pw_utils.rs @@ -11,7 +11,7 @@ use anyhow::Context as _; use calloop::timer::{TimeoutAction, Timer}; use calloop::RegistrationToken; use pipewire::context::Context; -use pipewire::core::Core; +use pipewire::core::{Core, PW_ID_CORE}; use pipewire::main_loop::MainLoop; use pipewire::properties::Properties; use pipewire::spa::buffer::DataType; @@ -54,12 +54,14 @@ const CAST_DELAY_ALLOWANCE: Duration = Duration::from_micros(100); pub struct PipeWire { _context: Context, pub core: Core, + pub token: RegistrationToken, to_niri: calloop::channel::Sender, } pub enum PwToNiri { StopCast { session_id: usize }, Redraw(CastTarget), + FatalError, } pub struct Cast { @@ -134,15 +136,26 @@ macro_rules! make_params { } impl PipeWire { - pub fn new(event_loop: &LoopHandle<'static, State>) -> anyhow::Result { + pub fn new( + event_loop: &LoopHandle<'static, State>, + to_niri: calloop::channel::Sender, + ) -> anyhow::Result { let main_loop = MainLoop::new(None).context("error creating MainLoop")?; let context = Context::new(&main_loop).context("error creating Context")?; let core = context.connect(None).context("error creating Core")?; + let to_niri_ = to_niri.clone(); let listener = core .add_listener_local() - .error(|id, seq, res, message| { + .error(move |id, seq, res, message| { warn!(id, seq, res, message, "pw error"); + + // Reset PipeWire on connection errors. + if id == PW_ID_CORE && res == -32 { + if let Err(err) = to_niri_.send(PwToNiri::FatalError) { + warn!("error sending FatalError to niri: {err:?}"); + } + } }) .register(); mem::forget(listener); @@ -154,7 +167,7 @@ impl PipeWire { } } let generic = Generic::new(AsFdWrapper(main_loop), Interest::READ, Mode::Level); - event_loop + let token = event_loop .insert_source(generic, move |_, wrapper, _| { let _span = tracy_client::span!("pipewire iteration"); wrapper.0.loop_().iterate(Duration::ZERO); @@ -162,17 +175,10 @@ impl PipeWire { }) .unwrap(); - let (to_niri, from_pipewire) = calloop::channel::channel(); - event_loop - .insert_source(from_pipewire, move |event, _, state| match event { - calloop::channel::Event::Msg(msg) => state.on_pw_msg(msg), - calloop::channel::Event::Closed => (), - }) - .unwrap(); - Ok(Self { _context: context, core, + token, to_niri, }) }