diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/dbus/mod.rs | 5 | ||||
| -rw-r--r-- | src/dbus/mutter_screen_cast.rs | 112 | ||||
| -rw-r--r-- | src/handlers/compositor.rs | 9 | ||||
| -rw-r--r-- | src/handlers/xdg_shell.rs | 6 | ||||
| -rw-r--r-- | src/niri.rs | 430 | ||||
| -rw-r--r-- | src/pw_utils.rs | 262 |
6 files changed, 685 insertions, 139 deletions
diff --git a/src/dbus/mod.rs b/src/dbus/mod.rs index 179f10a7..eab72ce7 100644 --- a/src/dbus/mod.rs +++ b/src/dbus/mod.rs @@ -87,11 +87,8 @@ impl DBusServers { let (to_niri, from_screen_cast) = calloop::channel::channel(); niri.event_loop .insert_source(from_screen_cast, { - let to_niri = to_niri.clone(); move |event, _, state| match event { - calloop::channel::Event::Msg(msg) => { - state.on_screen_cast_msg(&to_niri, msg) - } + calloop::channel::Event::Msg(msg) => state.on_screen_cast_msg(msg), calloop::channel::Event::Closed => (), } }) diff --git a/src/dbus/mutter_screen_cast.rs b/src/dbus/mutter_screen_cast.rs index 9c7ffb28..4b3c2fdd 100644 --- a/src/dbus/mutter_screen_cast.rs +++ b/src/dbus/mutter_screen_cast.rs @@ -4,7 +4,6 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use serde::Deserialize; -use smithay::output::Output; use zbus::fdo::RequestNameFlags; use zbus::zvariant::{DeserializeDict, OwnedObjectPath, SerializeDict, Type, Value}; use zbus::{dbus_interface, fdo, InterfaceRef, ObjectServer, SignalContext}; @@ -47,15 +46,40 @@ struct RecordMonitorProperties { _is_recording: Option<bool>, } +#[derive(Debug, DeserializeDict, Type)] +#[zvariant(signature = "dict")] +struct RecordWindowProperties { + #[zvariant(rename = "window-id")] + window_id: u64, + #[zvariant(rename = "cursor-mode")] + cursor_mode: Option<CursorMode>, + #[zvariant(rename = "is-recording")] + _is_recording: Option<bool>, +} + +static STREAM_ID: AtomicUsize = AtomicUsize::new(0); + #[derive(Clone)] pub struct Stream { - // FIXME: update on scale changes and whatnot. - output: niri_ipc::Output, + target: StreamTarget, cursor_mode: CursorMode, was_started: Arc<AtomicBool>, to_niri: calloop::channel::Sender<ScreenCastToNiri>, } +#[derive(Clone)] +enum StreamTarget { + // FIXME: update on scale changes and whatnot. + Output(niri_ipc::Output), + Window { id: u64 }, +} + +#[derive(Debug, Clone)] +pub enum StreamTargetId { + Output { name: String }, + Window { id: u64 }, +} + #[derive(Debug, SerializeDict, Type, Value)] #[zvariant(signature = "dict")] struct StreamParameters { @@ -68,14 +92,13 @@ struct StreamParameters { pub enum ScreenCastToNiri { StartCast { session_id: usize, - output: String, + target: StreamTargetId, cursor_mode: CursorMode, signal_ctx: SignalContext<'static>, }, StopCast { session_id: usize, }, - Redraw(Output), } #[dbus_interface(name = "org.gnome.Mutter.ScreenCast")] @@ -176,16 +199,51 @@ impl Session { return Err(fdo::Error::Failed("monitor is disabled".to_owned())); } - static NUMBER: AtomicUsize = AtomicUsize::new(0); let path = format!( "/org/gnome/Mutter/ScreenCast/Stream/u{}", - NUMBER.fetch_add(1, Ordering::SeqCst) + STREAM_ID.fetch_add(1, Ordering::SeqCst) + ); + let path = OwnedObjectPath::try_from(path).unwrap(); + + let cursor_mode = properties.cursor_mode.unwrap_or_default(); + + let target = StreamTarget::Output(output); + let stream = Stream::new(target, cursor_mode, self.to_niri.clone()); + match server.at(&path, stream.clone()).await { + Ok(true) => { + let iface = server.interface(&path).await.unwrap(); + self.streams.lock().unwrap().push((stream, iface)); + } + Ok(false) => return Err(fdo::Error::Failed("stream path already exists".to_owned())), + Err(err) => { + return Err(fdo::Error::Failed(format!( + "error creating stream object: {err:?}" + ))) + } + } + + Ok(path) + } + + async fn record_window( + &mut self, + #[zbus(object_server)] server: &ObjectServer, + properties: RecordWindowProperties, + ) -> fdo::Result<OwnedObjectPath> { + debug!(?properties, "record_window"); + + let path = format!( + "/org/gnome/Mutter/ScreenCast/Stream/u{}", + STREAM_ID.fetch_add(1, Ordering::SeqCst) ); let path = OwnedObjectPath::try_from(path).unwrap(); let cursor_mode = properties.cursor_mode.unwrap_or_default(); - let stream = Stream::new(output.clone(), cursor_mode, self.to_niri.clone()); + let target = StreamTarget::Window { + id: properties.window_id, + }; + let stream = Stream::new(target, cursor_mode, self.to_niri.clone()); match server.at(&path, stream.clone()).await { Ok(true) => { let iface = server.interface(&path).await.unwrap(); @@ -214,10 +272,21 @@ impl Stream { #[dbus_interface(property)] async fn parameters(&self) -> StreamParameters { - let logical = self.output.logical.as_ref().unwrap(); - StreamParameters { - position: (logical.x, logical.y), - size: (logical.width as i32, logical.height as i32), + match &self.target { + StreamTarget::Output(output) => { + let logical = output.logical.as_ref().unwrap(); + StreamParameters { + position: (logical.x, logical.y), + size: (logical.width as i32, logical.height as i32), + } + } + StreamTarget::Window { .. } => { + // Does any consumer need this? + StreamParameters { + position: (0, 0), + size: (1, 1), + } + } } } } @@ -275,13 +344,13 @@ impl Drop for Session { } impl Stream { - pub fn new( - output: niri_ipc::Output, + fn new( + target: StreamTarget, cursor_mode: CursorMode, to_niri: calloop::channel::Sender<ScreenCastToNiri>, ) -> Self { Self { - output, + target, cursor_mode, was_started: Arc::new(AtomicBool::new(false)), to_niri, @@ -295,7 +364,7 @@ impl Stream { let msg = ScreenCastToNiri::StartCast { session_id, - output: self.output.name.clone(), + target: self.target.make_id(), cursor_mode: self.cursor_mode, signal_ctx: ctxt, }; @@ -305,3 +374,14 @@ impl Stream { } } } + +impl StreamTarget { + fn make_id(&self) -> StreamTargetId { + match self { + StreamTarget::Output(output) => StreamTargetId::Output { + name: output.name.clone(), + }, + StreamTarget::Window { id } => StreamTargetId::Window { id: *id }, + } + } +} diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs index 845b720d..34030124 100644 --- a/src/handlers/compositor.rs +++ b/src/handlers/compositor.rs @@ -209,6 +209,9 @@ impl CompositorHandler for State { let window = mapped.window.clone(); let output = output.clone(); + #[cfg(feature = "xdp-gnome-screencast")] + let id = mapped.id(); + // This is a commit of a previously-mapped toplevel. let is_mapped = with_renderer_surface_state(surface, |state| state.buffer().is_some()) @@ -235,6 +238,12 @@ impl CompositorHandler for State { let active_window = self.niri.layout.active_window().map(|(m, _)| &m.window); let was_active = active_window == Some(&window); + #[cfg(feature = "xdp-gnome-screencast")] + self.niri + .stop_casts_for_target(crate::pw_utils::CastTarget::Window { + id: u64::from(id.get()), + }); + self.niri.layout.remove_window(&window); if was_active { diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 30b2f5d8..f3337786 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -466,6 +466,12 @@ impl XdgShellHandler for State { let window = mapped.window.clone(); let output = output.clone(); + #[cfg(feature = "xdp-gnome-screencast")] + self.niri + .stop_casts_for_target(crate::pw_utils::CastTarget::Window { + id: u64::from(mapped.id().get()), + }); + self.backend.with_primary_renderer(|renderer| { self.niri.layout.store_unmap_snapshot(renderer, &window); }); diff --git a/src/niri.rs b/src/niri.rs index 6767faaa..57fa455c 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -116,6 +116,8 @@ use crate::protocols::foreign_toplevel::{self, ForeignToplevelManagerState}; use crate::protocols::gamma_control::GammaControlManagerState; use crate::protocols::screencopy::{Screencopy, ScreencopyManagerState}; use crate::pw_utils::{Cast, PipeWire}; +#[cfg(feature = "xdp-gnome-screencast")] +use crate::pw_utils::{CastSizeChange, CastTarget, PwToNiri}; use crate::render_helpers::debug::draw_opaque_regions; use crate::render_helpers::primary_gpu_texture::PrimaryGpuTextureRenderElement; use crate::render_helpers::renderer::NiriRenderer; @@ -277,6 +279,10 @@ pub struct Niri { // Casts are dropped before PipeWire to prevent a double-free (yay). pub casts: Vec<Cast>, pub pipewire: Option<PipeWire>, + + // Screencast output for each mapped window. + #[cfg(feature = "xdp-gnome-screencast")] + pub mapped_cast_output: HashMap<Window, Output>, } pub struct OutputState { @@ -508,6 +514,9 @@ impl State { foreign_toplevel::refresh(self); self.niri.refresh_window_rules(); self.refresh_ipc_outputs(); + + #[cfg(feature = "xdp-gnome-screencast")] + self.niri.refresh_mapped_cast_outputs(); } pub fn move_cursor(&mut self, location: Point<f64, Logical>) { @@ -1172,15 +1181,34 @@ impl State { } #[cfg(feature = "xdp-gnome-screencast")] - pub fn on_screen_cast_msg( - &mut self, - to_niri: &calloop::channel::Sender<ScreenCastToNiri>, - msg: ScreenCastToNiri, - ) { + pub fn on_pw_msg(&mut self, msg: PwToNiri) { + match msg { + PwToNiri::StopCast { session_id } => self.niri.stop_cast(session_id), + PwToNiri::Redraw(target) => match target { + CastTarget::Output(weak) => { + if let Some(output) = weak.upgrade() { + self.niri.queue_redraw(&output); + } + } + CastTarget::Window { id } => { + self.backend.with_primary_renderer(|renderer| { + // FIXME: target presentation time at the time of window commit? + self.niri + .render_window_for_screen_cast(renderer, id, get_monotonic_time()); + }); + } + }, + } + } + + #[cfg(feature = "xdp-gnome-screencast")] + pub fn on_screen_cast_msg(&mut self, msg: ScreenCastToNiri) { + use crate::dbus::mutter_screen_cast::StreamTargetId; + match msg { ScreenCastToNiri::StartCast { session_id, - output, + target, cursor_mode, signal_ctx, } => { @@ -1201,25 +1229,65 @@ impl State { return; }; - let Some(output) = self - .niri - .global_space - .outputs() - .find(|out| out.name() == output) - .cloned() - else { - warn!("tried to start a screencast on missing output: {output}"); - return; + let (target, size, refresh) = match target { + StreamTargetId::Output { name } => { + let global_space = &self.niri.global_space; + let output = global_space.outputs().find(|out| out.name() == name); + let Some(output) = output else { + warn!("error starting screencast: requested output is missing"); + self.niri.stop_cast(session_id); + return; + }; + + let mode = output.current_mode().unwrap(); + let transform = output.current_transform(); + let size = transform.transform_size(mode.size); + let refresh = mode.refresh as u32; + (CastTarget::Output(output.downgrade()), size, refresh) + } + StreamTargetId::Window { id } => { + let mut window = None; + self.niri.layout.with_windows(|mapped, _| { + if u64::from(mapped.id().get()) != id { + return; + } + + window = Some(mapped.window.clone()); + }); + + let Some(window) = window else { + warn!("error starting screencast: requested window is missing"); + self.niri.stop_cast(session_id); + return; + }; + + // Use the cached output since it will be present even if the output was + // currently disconnected. + let Some(output) = self.niri.mapped_cast_output.get(&window) else { + warn!("error starting screencast: requested window is missing"); + self.niri.stop_cast(session_id); + return; + }; + + let scale = Scale::from(output.current_scale().fractional_scale()); + let bbox = window.bbox_with_popups(); + let size = bbox.size.to_physical_precise_ceil(scale); + let refresh = output.current_mode().unwrap().refresh as u32; + + (CastTarget::Window { id }, size, refresh) + } }; - match pw.start_cast( - to_niri.clone(), + let res = pw.start_cast( gbm, session_id, - output, + target, + size, + refresh, cursor_mode, signal_ctx, - ) { + ); + match res { Ok(cast) => { self.niri.casts.push(cast); } @@ -1230,7 +1298,6 @@ impl State { } } ScreenCastToNiri::StopCast { session_id } => self.niri.stop_cast(session_id), - ScreenCastToNiri::Redraw(output) => self.niri.queue_redraw(&output), } } @@ -1631,6 +1698,9 @@ impl Niri { pipewire, casts: vec![], + + #[cfg(feature = "xdp-gnome-screencast")] + mapped_cast_output: HashMap::new(), } } @@ -1860,6 +1930,9 @@ impl Niri { RedrawState::WaitingForEstimatedVBlankAndQueued(token) => self.event_loop.remove(token), } + #[cfg(feature = "xdp-gnome-screencast")] + self.stop_casts_for_target(CastTarget::Output(output.downgrade())); + // Disable the output global and remove some time later to give the clients some time to // process it. let global = state.global; @@ -2574,6 +2647,54 @@ impl Niri { } } + #[cfg(feature = "xdp-gnome-screencast")] + pub fn refresh_mapped_cast_outputs(&mut self) { + use std::collections::hash_map::Entry; + + let mut seen = HashSet::new(); + let mut output_changed = vec![]; + + self.layout.with_windows(|mapped, output| { + seen.insert(mapped.window.clone()); + + let Some(output) = output else { + return; + }; + + match self.mapped_cast_output.entry(mapped.window.clone()) { + Entry::Occupied(mut entry) => { + if entry.get() != output { + entry.insert(output.clone()); + output_changed.push((mapped.id(), output.clone())); + } + } + Entry::Vacant(entry) => { + entry.insert(output.clone()); + } + } + }); + + self.mapped_cast_output.retain(|win, _| seen.contains(win)); + + let mut to_stop = vec![]; + for (id, out) in output_changed { + let refresh = out.current_mode().unwrap().refresh as u32; + let target = CastTarget::Window { + id: u64::from(id.get()), + }; + for cast in self.casts.iter_mut().filter(|cast| cast.target == target) { + if let Err(err) = cast.set_refresh(refresh) { + warn!("error changing cast FPS: {err:?}"); + to_stop.push(cast.session_id); + }; + } + } + + for session_id in to_stop { + self.stop_cast(session_id); + } + } + pub fn render<R: NiriRenderer>( &self, renderer: &mut R, @@ -2859,6 +2980,11 @@ impl Niri { backend.with_primary_renderer(|renderer| { // Render and send to PipeWire screencast streams. self.render_for_screen_cast(renderer, output, target_presentation_time); + + // FIXME: when a window is hidden, it should probably still receive frame callbacks and + // get rendered for screen cast. This is currently unimplemented, but happens to work + // by chance, since output redrawing is more eager than it should be. + self.render_windows_for_screen_cast(renderer, output, target_presentation_time); }); } @@ -3294,10 +3420,10 @@ impl Niri { output: &Output, target_presentation_time: Duration, ) { - use crate::render_helpers::render_to_dmabuf; - let _span = tracy_client::span!("Niri::render_for_screen_cast"); + let target = CastTarget::Output(output.downgrade()); + let size = output.current_mode().unwrap().size; let transform = output.current_transform(); let size = transform.transform_size(size); @@ -3313,84 +3439,213 @@ impl Niri { continue; } - if &cast.output != output { + if cast.target != target { continue; } - if cast.size.get() != size { - if cast.pending_size.get() != size { - debug!("output size changed, updating stream size"); - if let Err(err) = cast.set_size(size) { - warn!("error updating stream size, stopping screencast: {err:?}"); - casts_to_stop.push(cast.session_id); - } - } else { - debug!("stream size still hasn't changed, skipping frame"); + match cast.ensure_size(size) { + Ok(CastSizeChange::Ready) => (), + Ok(CastSizeChange::Pending) => continue, + Err(err) => { + warn!("error updating stream size, stopping screencast: {err:?}"); + casts_to_stop.push(cast.session_id); } + } - // Even in the successful case, we'll need to wait till the size actually changes. + if cast.should_skip_frame(target_presentation_time) { continue; } - let last = cast.last_frame_time; - let min = cast.min_time_between_frames.get(); - if last.is_zero() { - trace!(?target_presentation_time, ?last, "last is zero, recording"); - } else if target_presentation_time < last { - // Record frame with a warning; in case it was an overflow this will fix it. - warn!( - ?target_presentation_time, - ?last, - "target presentation time is below last, did it overflow or did we mispredict?" - ); - } else { - let diff = target_presentation_time - last; - if diff < min { - trace!( - ?target_presentation_time, - ?last, - "skipping frame because it is too soon: diff={diff:?} < min={min:?}", - ); - continue; + // FIXME: Hidden / embedded / metadata cursor + let elements = elements.get_or_insert_with(|| { + self.render(renderer, output, true, RenderTarget::Screencast) + }); + let elements = elements.iter().rev(); + + if cast.dequeue_buffer_and_render(renderer, elements, size, scale) { + cast.last_frame_time = target_presentation_time; + } + } + self.casts = casts; + + for id in casts_to_stop { + self.stop_cast(id); + } + } + + #[cfg(feature = "xdp-gnome-screencast")] + fn render_windows_for_screen_cast( + &mut self, + renderer: &mut GlesRenderer, + output: &Output, + target_presentation_time: Duration, + ) { + let _span = tracy_client::span!("Niri::render_windows_for_screen_cast"); + + let scale = Scale::from(output.current_scale().fractional_scale()); + + let mut casts_to_stop = vec![]; + + let mut casts = mem::take(&mut self.casts); + for cast in &mut casts { + if !cast.is_active.get() { + continue; + } + + let CastTarget::Window { id } = cast.target else { + continue; + }; + + let mut windows = self.layout.windows_for_output(output); + let Some(mapped) = windows.find(|win| u64::from(win.id().get()) == id) else { + continue; + }; + + let bbox = mapped.window.bbox_with_popups(); + let size = bbox.size.to_physical_precise_ceil(scale); + + match cast.ensure_size(size) { + Ok(CastSizeChange::Ready) => (), + Ok(CastSizeChange::Pending) => continue, + Err(err) => { + warn!("error updating stream size, stopping screencast: {err:?}"); + casts_to_stop.push(cast.session_id); } } - { - let mut buffer = match cast.stream.dequeue_buffer() { - Some(buffer) => buffer, - None => { - warn!("no available buffer in pw stream, skipping frame"); - continue; - } - }; + if cast.should_skip_frame(target_presentation_time) { + continue; + } - let data = &mut buffer.datas_mut()[0]; - let fd = data.as_raw().fd as i32; - let dmabuf = cast.dmabufs.borrow()[&fd].clone(); + let alpha = if mapped.is_fullscreen() { + 1. + } else { + mapped.rules().opacity.unwrap_or(1.).clamp(0., 1.) + }; + // FIXME: pointer. + let elements = mapped.render( + renderer, + mapped.window.geometry().loc.to_f64(), + scale, + alpha, + RenderTarget::Screencast, + ); + let geo = elements + .iter() + .map(|ele| ele.geometry(scale)) + .reduce(|a, b| a.merge(b)) + .unwrap_or_default(); + let elements = elements.iter().rev().map(|elem| { + RelocateRenderElement::from_element(elem, geo.loc.upscale(-1), Relocate::Relative) + }); - // FIXME: Hidden / embedded / metadata cursor - let elements = elements.get_or_insert_with(|| { - self.render::<GlesRenderer>(renderer, output, true, RenderTarget::Screencast) - }); - let elements = elements.iter().rev(); + if cast.dequeue_buffer_and_render(renderer, elements, size, scale) { + cast.last_frame_time = target_presentation_time; + } + } + self.casts = casts; - if let Err(err) = - render_to_dmabuf(renderer, dmabuf, size, scale, Transform::Normal, elements) - { - warn!("error rendering to dmabuf: {err:?}"); - continue; + for id in casts_to_stop { + self.stop_cast(id); + } + } + + #[cfg(feature = "xdp-gnome-screencast")] + fn render_window_for_screen_cast( + &mut self, + renderer: &mut GlesRenderer, + window_id: u64, + target_presentation_time: Duration, + ) { + let _span = tracy_client::span!("Niri::render_window_for_screen_cast"); + + let mut window = None; + self.layout.with_windows(|mapped, _| { + if u64::from(mapped.id().get()) != window_id { + return; + } + + window = Some(mapped.window.clone()); + }); + + let Some(window) = window else { + return; + }; + + // Use the cached output since it will be present even if the output was + // currently disconnected. + let Some(output) = self.mapped_cast_output.get(&window) else { + return; + }; + + let mut windows = self.layout.windows_for_output(output); + let mapped = windows + .find(|mapped| u64::from(mapped.id().get()) == window_id) + .unwrap(); + + let scale = Scale::from(output.current_scale().fractional_scale()); + let bbox = mapped.window.bbox_with_popups(); + let size = bbox.size.to_physical_precise_ceil(scale); + + let mut elements = None; + let mut casts_to_stop = vec![]; + + let mut casts = mem::take(&mut self.casts); + for cast in &mut casts { + if !cast.is_active.get() { + continue; + } + + if cast.target != (CastTarget::Window { id: window_id }) { + continue; + } + + match cast.ensure_size(size) { + Ok(CastSizeChange::Ready) => (), + Ok(CastSizeChange::Pending) => continue, + Err(err) => { + warn!("error updating stream size, stopping screencast: {err:?}"); + casts_to_stop.push(cast.session_id); } + } - let maxsize = data.as_raw().maxsize; - let chunk = data.chunk_mut(); - *chunk.size_mut() = maxsize; - *chunk.stride_mut() = maxsize as i32 / size.h; + if cast.should_skip_frame(target_presentation_time) { + continue; } - cast.last_frame_time = target_presentation_time; + let (elements, geo) = elements.get_or_insert_with(|| { + let alpha = if mapped.is_fullscreen() { + 1. + } else { + mapped.rules().opacity.unwrap_or(1.).clamp(0., 1.) + }; + // FIXME: pointer. + let elements = mapped.render( + renderer, + mapped.window.geometry().loc.to_f64(), + scale, + alpha, + RenderTarget::Screencast, + ); + let geo = elements + .iter() + .map(|ele| ele.geometry(scale)) + .reduce(|a, b| a.merge(b)) + .unwrap_or_default(); + (elements, geo) + }); + let elements = elements.iter().rev().map(|elem| { + RelocateRenderElement::from_element(elem, geo.loc.upscale(-1), Relocate::Relative) + }); + + if cast.dequeue_buffer_and_render(renderer, elements, size, scale) { + cast.last_frame_time = target_presentation_time; + } } self.casts = casts; + drop(windows); + for id in casts_to_stop { self.stop_cast(id); } @@ -3472,6 +3727,23 @@ impl Niri { } } + #[cfg(feature = "xdp-gnome-screencast")] + pub fn stop_casts_for_target(&mut self, target: CastTarget) { + let _span = tracy_client::span!("Niri::stop_casts_for_target"); + + // This is O(N^2) but it shouldn't be a problem I think. + let ids: Vec<_> = self + .casts + .iter() + .filter(|cast| cast.target == target) + .map(|cast| cast.session_id) + .collect(); + + for id in ids { + self.stop_cast(id); + } + } + pub fn debug_toggle_damage(&mut self) { self.debug_draw_damage = !self.debug_draw_damage; diff --git a/src/pw_utils.rs b/src/pw_utils.rs index cb63cff7..eedd0f03 100644 --- a/src/pw_utils.rs +++ b/src/pw_utils.rs @@ -27,19 +27,28 @@ use smithay::backend::allocator::dmabuf::{AsDmabuf, Dmabuf}; use smithay::backend::allocator::gbm::{GbmBuffer, GbmBufferFlags, GbmDevice}; use smithay::backend::allocator::Fourcc; use smithay::backend::drm::DrmDeviceFd; -use smithay::output::Output; +use smithay::backend::renderer::element::RenderElement; +use smithay::backend::renderer::gles::GlesRenderer; +use smithay::output::WeakOutput; use smithay::reexports::calloop::generic::Generic; use smithay::reexports::calloop::{Interest, LoopHandle, Mode, PostAction}; use smithay::reexports::gbm::Modifier; -use smithay::utils::{Physical, Size}; +use smithay::utils::{Physical, Scale, Size, Transform}; use zbus::SignalContext; -use crate::dbus::mutter_screen_cast::{self, CursorMode, ScreenCastToNiri}; +use crate::dbus::mutter_screen_cast::{self, CursorMode}; use crate::niri::State; +use crate::render_helpers::render_to_dmabuf; pub struct PipeWire { _context: Context, pub core: Core, + to_niri: calloop::channel::Sender<PwToNiri>, +} + +pub enum PwToNiri { + StopCast { session_id: usize }, + Redraw(CastTarget), } pub struct Cast { @@ -47,9 +56,8 @@ pub struct Cast { pub stream: Stream, _listener: StreamListener<()>, pub is_active: Rc<Cell<bool>>, - pub output: Output, - pub size: Rc<Cell<Size<i32, Physical>>>, - pub pending_size: Rc<Cell<Size<i32, Physical>>>, + pub target: CastTarget, + pub size: Rc<Cell<CastSize>>, pub refresh: u32, pub cursor_mode: CursorMode, pub last_frame_time: Duration, @@ -57,6 +65,28 @@ pub struct Cast { pub dmabufs: Rc<RefCell<HashMap<i32, Dmabuf>>>, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CastSize { + InitialPending(Size<i32, Physical>), + Ready(Size<i32, Physical>), + ChangePending { + last_negotiated: Size<i32, Physical>, + pending: Size<i32, Physical>, + }, +} + +#[derive(PartialEq, Eq)] +pub enum CastSizeChange { + Ready, + Pending, +} + +#[derive(Clone, PartialEq, Eq)] +pub enum CastTarget { + Output(WeakOutput), + Window { id: u64 }, +} + impl PipeWire { pub fn new(event_loop: &LoopHandle<'static, State>) -> anyhow::Result<Self> { let main_loop = MainLoop::new(None).context("error creating MainLoop")?; @@ -86,45 +116,49 @@ 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, + to_niri, }) } + #[allow(clippy::too_many_arguments)] pub fn start_cast( &self, - to_niri: calloop::channel::Sender<ScreenCastToNiri>, gbm: GbmDevice<DrmDeviceFd>, session_id: usize, - output: Output, + target: CastTarget, + size: Size<i32, Physical>, + refresh: u32, cursor_mode: CursorMode, signal_ctx: SignalContext<'static>, ) -> anyhow::Result<Cast> { let _span = tracy_client::span!("PipeWire::start_cast"); - let to_niri_ = to_niri.clone(); + let to_niri_ = self.to_niri.clone(); let stop_cast = move || { - if let Err(err) = to_niri_.send(ScreenCastToNiri::StopCast { session_id }) { + if let Err(err) = to_niri_.send(PwToNiri::StopCast { session_id }) { warn!("error sending StopCast to niri: {err:?}"); } }; - let weak = output.downgrade(); + let target_ = target.clone(); + let to_niri_ = self.to_niri.clone(); let redraw = move || { - if let Some(output) = weak.upgrade() { - if let Err(err) = to_niri.send(ScreenCastToNiri::Redraw(output)) { - warn!("error sending Redraw to niri: {err:?}"); - } + if let Err(err) = to_niri_.send(PwToNiri::Redraw(target_.clone())) { + warn!("error sending Redraw to niri: {err:?}"); } }; let redraw_ = redraw.clone(); - let mode = output.current_mode().unwrap(); - let size = mode.size; - let transform = output.current_transform(); - let size = transform.transform_size(size); - let refresh = mode.refresh as u32; - let stream = Stream::new(&self.core, "niri-screen-cast-src", Properties::new()) .context("error creating Stream")?; @@ -133,8 +167,9 @@ impl PipeWire { let is_active = Rc::new(Cell::new(false)); let min_time_between_frames = Rc::new(Cell::new(Duration::ZERO)); let dmabufs = Rc::new(RefCell::new(HashMap::new())); - let pending_size = Rc::new(Cell::new(size)); - let size = Rc::new(Cell::new(Size::from((0, 0)))); + + let pending_size = size; + let size = Rc::new(Cell::new(CastSize::InitialPending(size))); let listener = stream .add_local_listener_with_user_data(()) @@ -186,7 +221,6 @@ impl PipeWire { .param_changed({ let min_time_between_frames = min_time_between_frames.clone(); let size = size.clone(); - let pending_size = pending_size.clone(); move |stream, (), id, pod| { let id = ParamType::from_raw(id); trace!(?id, "pw stream: param_changed"); @@ -213,9 +247,18 @@ impl PipeWire { |
