diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2025-03-15 11:23:01 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-03-15 09:55:46 -0700 |
| commit | 31891e6642481c018357354583db804657c09c53 (patch) | |
| tree | f52ffe3fe28ebee0a7b93112285ed3a09e4c90b6 /src | |
| parent | 392fc27de110d3548095e465d5cb38bd8d5730ea (diff) | |
| download | niri-31891e6642481c018357354583db804657c09c53.tar.gz niri-31891e6642481c018357354583db804657c09c53.tar.bz2 niri-31891e6642481c018357354583db804657c09c53.zip | |
Implement dynamic screencast target
Diffstat (limited to 'src')
| -rw-r--r-- | src/input/mod.rs | 32 | ||||
| -rw-r--r-- | src/niri.rs | 129 | ||||
| -rw-r--r-- | src/pw_utils.rs | 3 | ||||
| -rw-r--r-- | src/window/mapped.rs | 2 |
4 files changed, 158 insertions, 8 deletions
diff --git a/src/input/mod.rs b/src/input/mod.rs index 95d30457..08bebe40 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -41,7 +41,7 @@ use self::resize_grab::ResizeGrab; use self::spatial_movement_grab::SpatialMovementGrab; use crate::layout::scrolling::ScrollDirection; use crate::layout::LayoutElement as _; -use crate::niri::State; +use crate::niri::{CastTarget, State}; use crate::ui::screenshot_ui::ScreenshotUi; use crate::utils::spawning::spawn; use crate::utils::{center, get_monotonic_time, ResizeEdge}; @@ -1786,6 +1786,36 @@ impl State { } } } + Action::SetDynamicCastWindow => { + let id = self + .niri + .layout + .active_workspace() + .and_then(|ws| ws.active_window()) + .map(|mapped| mapped.id().get()); + if let Some(id) = id { + self.set_dynamic_cast_target(CastTarget::Window { id }); + } + } + Action::SetDynamicCastWindowById(id) => { + let layout = &self.niri.layout; + if layout.windows().any(|(_, mapped)| mapped.id().get() == id) { + self.set_dynamic_cast_target(CastTarget::Window { id }); + } + } + Action::SetDynamicCastMonitor(output) => { + let output = match output { + None => self.niri.layout.active_output(), + Some(name) => self.niri.output_by_name_match(&name), + }; + if let Some(output) = output { + let output = output.downgrade(); + self.set_dynamic_cast_target(CastTarget::Output(output)); + } + } + Action::ClearDynamicCastTarget => { + self.set_dynamic_cast_target(CastTarget::Nothing); + } } } diff --git a/src/niri.rs b/src/niri.rs index 3ff71d09..c30ea255 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -379,6 +379,10 @@ pub struct Niri { // Screencast output for each mapped window. #[cfg(feature = "xdp-gnome-screencast")] pub mapped_cast_output: HashMap<Window, Output>, + + /// Window ID for the "dynamic cast" special window for the xdp-gnome picker. + #[cfg(feature = "xdp-gnome-screencast")] + pub dynamic_cast_id_for_portal: MappedId, } #[derive(Debug)] @@ -510,6 +514,8 @@ pub enum CenterCoords { #[derive(Clone, PartialEq, Eq)] pub enum CastTarget { + // Dynamic cast before selecting anything. + Nothing, Output(WeakOutput), Window { id: u64 }, } @@ -1623,6 +1629,29 @@ impl State { }; match &cast.target { + CastTarget::Nothing => { + // Matches what we create the dynamic source with. + let size = Size::from((1, 1)); + let scale = Scale::from(1.); + + match cast.ensure_size(size) { + Ok(CastSizeChange::Ready) => (), + Ok(CastSizeChange::Pending) => return, + Err(err) => { + warn!("error updating stream size, stopping screencast: {err:?}"); + let session_id = cast.session_id; + self.niri.stop_cast(session_id); + return; + } + } + + self.backend.with_primary_renderer(|renderer| { + let elements: &[MonitorRenderElement<_>] = &[]; + if cast.dequeue_buffer_and_render(renderer, elements, size, scale) { + cast.last_frame_time = get_monotonic_time(); + } + }); + } CastTarget::Output(weak) => { if let Some(output) = weak.upgrade() { self.niri.queue_redraw(&output); @@ -1673,6 +1702,57 @@ impl State { } } + #[cfg(not(feature = "xdp-gnome-screencast"))] + pub fn set_dynamic_cast_target(&mut self, _target: CastTarget) {} + + #[cfg(feature = "xdp-gnome-screencast")] + pub fn set_dynamic_cast_target(&mut self, target: CastTarget) { + let _span = tracy_client::span!("State::set_dynamic_cast_target"); + + let mut refresh = None; + match &target { + // Leave refresh as is when clearing. Chances are, the next refresh will match it, + // then we'll avoid reconfiguring. + CastTarget::Nothing => (), + CastTarget::Output(output) => { + if let Some(output) = output.upgrade() { + refresh = Some(output.current_mode().unwrap().refresh as u32); + } + } + CastTarget::Window { id } => { + let mut windows = self.niri.layout.windows(); + if let Some((_, mapped)) = windows.find(|(_, mapped)| mapped.id().get() == *id) { + if let Some(output) = self.niri.mapped_cast_output.get(&mapped.window) { + refresh = Some(output.current_mode().unwrap().refresh as u32); + } + } + } + } + + let mut to_redraw = Vec::new(); + let mut to_stop = Vec::new(); + for cast in &mut self.niri.casts { + if !cast.dynamic_target { + continue; + } + + if let Some(refresh) = refresh { + if let Err(err) = cast.set_refresh(refresh) { + warn!("error changing cast FPS: {err:?}"); + to_stop.push(cast.session_id); + continue; + } + } + + cast.target = target.clone(); + to_redraw.push(cast.stream_id); + } + + for id in to_redraw { + self.redraw_cast(id); + } + } + #[cfg(feature = "xdp-gnome-screencast")] pub fn on_screen_cast_msg(&mut self, msg: ScreenCastToNiri) { use smithay::reexports::gbm::Modifier; @@ -1712,6 +1792,7 @@ impl State { } }; + let mut dynamic_target = false; let (target, size, refresh, alpha) = match target { StreamTargetId::Output { name } => { let global_space = &self.niri.global_space; @@ -1728,6 +1809,15 @@ impl State { let refresh = mode.refresh as u32; (CastTarget::Output(output.downgrade()), size, refresh, false) } + StreamTargetId::Window { id } + if id == self.niri.dynamic_cast_id_for_portal.get() => + { + dynamic_target = true; + + // All dynamic casts start as Nothing to avoid surprises and exposing + // sensitive info. + (CastTarget::Nothing, Size::from((1, 1)), 1000, true) + } StreamTargetId::Window { id } => { let Some(window) = self.niri.layout.windows().find_map(|(_, mapped)| { (mapped.id().get() == id).then_some(&mapped.window) @@ -1776,6 +1866,7 @@ impl State { session_id, stream_id, target, + dynamic_target, size, refresh, alpha, @@ -1851,6 +1942,15 @@ impl State { let mut windows = HashMap::new(); + #[cfg(feature = "xdp-gnome-screencast")] + windows.insert( + self.niri.dynamic_cast_id_for_portal.get(), + gnome_shell_introspect::WindowProperties { + title: String::from("niri Dynamic Cast Target"), + app_id: String::from("rs.bxt.niri"), + }, + ); + self.niri.layout.with_windows(|mapped, _, _| { let id = mapped.id().get(); let props = with_toplevel_role(mapped.toplevel(), |role| { @@ -2260,6 +2360,9 @@ impl Niri { #[cfg(feature = "xdp-gnome-screencast")] mapped_cast_output: HashMap::new(), + + #[cfg(feature = "xdp-gnome-screencast")] + dynamic_cast_id_for_portal: MappedId::next(), }; niri.reset_pointer_inactivity_timer(); @@ -4598,16 +4701,30 @@ impl Niri { 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(); + let mut saw_dynamic = false; + let mut ids = Vec::new(); + for cast in &self.casts { + if cast.target != target { + continue; + } + + if cast.dynamic_target { + saw_dynamic = true; + continue; + } + + ids.push(cast.session_id); + } for id in ids { self.stop_cast(id); } + + // We don't stop dynamic casts, instead we switch them to Nothing. + if saw_dynamic { + self.event_loop + .insert_idle(|state| state.set_dynamic_cast_target(CastTarget::Nothing)); + } } pub fn remove_screencopy_output(&mut self, output: &Output) { diff --git a/src/pw_utils.rs b/src/pw_utils.rs index 555da333..f6ad4014 100644 --- a/src/pw_utils.rs +++ b/src/pw_utils.rs @@ -71,6 +71,7 @@ pub struct Cast { _listener: StreamListener<()>, pub is_active: Rc<Cell<bool>>, pub target: CastTarget, + pub dynamic_target: bool, formats: FormatSet, state: Rc<RefCell<CastState>>, refresh: Rc<Cell<u32>>, @@ -186,6 +187,7 @@ impl PipeWire { session_id: usize, stream_id: usize, target: CastTarget, + dynamic_target: bool, size: Size<i32, Physical>, refresh: u32, alpha: bool, @@ -651,6 +653,7 @@ impl PipeWire { _listener: listener, is_active, target, + dynamic_target, formats, state, refresh, diff --git a/src/window/mapped.rs b/src/window/mapped.rs index ac508990..16dfc4d4 100644 --- a/src/window/mapped.rs +++ b/src/window/mapped.rs @@ -136,7 +136,7 @@ static MAPPED_ID_COUNTER: IdCounter = IdCounter::new(); pub struct MappedId(u64); impl MappedId { - fn next() -> MappedId { + pub fn next() -> MappedId { MappedId(MAPPED_ID_COUNTER.next()) } |
