Skip to content

Commit

Permalink
Restart PipeWire on errors
Browse files Browse the repository at this point in the history
This lets you restart pipewire and then get a screencast successfully.
  • Loading branch information
YaLTeR committed Jan 4, 2025
1 parent bf6995f commit b2ca280
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 27 deletions.
56 changes: 41 additions & 15 deletions src/niri.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -345,7 +345,9 @@ pub struct Niri {

// Casts are dropped before PipeWire to prevent a double-free (yay).
pub casts: Vec<Cast>,
pub pipewire: LazyCell<Option<PipeWire>, Box<dyn FnOnce() -> Option<PipeWire>>>,
pub pipewire: Option<PipeWire>,
#[cfg(feature = "xdp-gnome-screencast")]
pub pw_to_niri: calloop::channel::Sender<PwToNiri>,

// Screencast output for each mapped window.
#[cfg(feature = "xdp-gnome-screencast")]
Expand Down Expand Up @@ -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);
}
}
}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand Down
30 changes: 18 additions & 12 deletions src/pw_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<PwToNiri>,
}

pub enum PwToNiri {
StopCast { session_id: usize },
Redraw(CastTarget),
FatalError,
}

pub struct Cast {
Expand Down Expand Up @@ -134,15 +136,26 @@ macro_rules! make_params {
}

impl PipeWire {
pub fn new(event_loop: &LoopHandle<'static, State>) -> anyhow::Result<Self> {
pub fn new(
event_loop: &LoopHandle<'static, State>,
to_niri: calloop::channel::Sender<PwToNiri>,
) -> anyhow::Result<Self> {
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);
Expand All @@ -154,25 +167,18 @@ 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);
Ok(PostAction::Continue)
})
.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,
})
}
Expand Down

0 comments on commit b2ca280

Please sign in to comment.