ipc: Add screencopy cast tracking

Track wlr-screencopy sessions that use with_damage as screencasts. These
are used by tools like wl-screenrec for continuous recording.
This commit is contained in:
Ivan Molodetskikh
2026-01-12 18:28:27 +03:00
parent 238caaf8da
commit e82830c68c
3 changed files with 128 additions and 36 deletions
+33 -3
View File
@@ -798,7 +798,6 @@ impl State {
server.send_event(event);
}
#[cfg(feature = "xdp-gnome-screencast")]
pub fn ipc_refresh_casts(&mut self) {
let Some(server) = &self.niri.ipc_server else {
return;
@@ -812,13 +811,16 @@ impl State {
let mut events = Vec::new();
let mut seen = HashSet::new();
// Check PipeWire screencasts.
#[cfg(feature = "xdp-gnome-screencast")]
{
// Check pending dynamic casts.
for pending in &self.niri.casting.pending_dynamic_casts {
let stream_id = pending.stream_id.get();
seen.insert(stream_id);
// Pending dynamic casts don't change any properties, so we only need to check if it's
// missing from the state.
// Pending dynamic casts don't change any properties, so we only need to check if
// it's missing from the state.
if !state.casts.contains_key(&stream_id) {
let cast = niri_ipc::Cast {
session_id: pending.session_id.get(),
@@ -850,6 +852,34 @@ impl State {
events.push(Event::CastStartedOrChanged { cast });
}
}
}
// Check screencopy casts.
for queue in self.niri.screencopy_state.queues() {
if let Some(cast_info) = queue.cast() {
let stream_id = cast_info.stream_id.get();
seen.insert(stream_id);
if state.casts.get(&stream_id).is_none_or(|existing| {
// Only this property can change.
match &existing.target {
niri_ipc::CastTarget::Output { name } => *name != cast_info.output_name,
_ => true,
}
}) {
let cast = niri_ipc::Cast {
session_id: cast_info.session_id.get(),
stream_id,
target: niri_ipc::CastTarget::Output {
name: cast_info.output_name.clone(),
},
is_dynamic_target: false,
is_active: true,
};
events.push(Event::CastStartedOrChanged { cast });
}
}
}
// Check for stopped casts.
for stream_id in state.casts.keys() {
-1
View File
@@ -816,7 +816,6 @@ impl State {
// screencasts.
#[cfg(feature = "xdp-gnome-screencast")]
self.niri.refresh_mapped_cast_window_rules();
#[cfg(feature = "xdp-gnome-screencast")]
self.ipc_refresh_casts();
self.niri.refresh_window_rules();
+66 -3
View File
@@ -9,7 +9,7 @@ use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::allocator::{Buffer, Fourcc};
use smithay::backend::renderer::damage::OutputDamageTracker;
use smithay::backend::renderer::sync::SyncPoint;
use smithay::output::Output;
use smithay::output::{Output, WeakOutput};
use smithay::reexports::wayland_protocols_wlr::screencopy::v1::server::{
zwlr_screencopy_frame_v1, zwlr_screencopy_manager_v1,
};
@@ -23,7 +23,7 @@ use smithay::wayland::{dmabuf, shm};
use zwlr_screencopy_frame_v1::{Flags, ZwlrScreencopyFrameV1};
use zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1;
use crate::utils::get_monotonic_time;
use crate::utils::{get_monotonic_time, CastSessionId, CastStreamId};
const VERSION: u32 = 3;
@@ -33,6 +33,40 @@ pub struct ScreencopyQueue {
pending_frames: HashSet<ZwlrScreencopyFrameV1>,
/// Queue of screencopies waiting for a corresponding output redraw with damage.
screencopies: Vec<Screencopy>,
/// Cast tracking, set when the first with_damage request arrives.
cast: Option<ScreencopyCast>,
}
pub struct ScreencopyCast {
pub session_id: CastSessionId,
pub stream_id: CastStreamId,
/// Output being captured.
///
/// Generally equal to the front entry in the queue, and persisted here when the queue becomes
/// empty.
pub output: WeakOutput,
/// Cached name of the output.
pub output_name: String,
}
impl ScreencopyCast {
fn new(output: &Output) -> Self {
Self {
session_id: CastSessionId::next(),
stream_id: CastStreamId::next(),
output: output.downgrade(),
output_name: output.name(),
}
}
fn update_output(&mut self, output: &Output) {
// Only allocate a new name when the output differs.
let weak = output.downgrade();
if self.output != weak {
self.output = weak;
self.output_name = output.name();
}
}
}
impl Default for ScreencopyQueue {
@@ -47,6 +81,7 @@ impl ScreencopyQueue {
damage_tracker: OutputDamageTracker::new((0, 0), 1.0, Transform::Normal),
pending_frames: HashSet::new(),
screencopies: Vec::new(),
cast: None,
}
}
@@ -54,6 +89,11 @@ impl ScreencopyQueue {
self.pending_frames.is_empty() && self.screencopies.is_empty()
}
/// Get the cast tracking info, if this queue is tracking a cast.
pub fn cast(&self) -> Option<&ScreencopyCast> {
self.cast.as_ref()
}
pub fn split(&mut self) -> (&mut OutputDamageTracker, Option<&Screencopy>) {
let ScreencopyQueue {
damage_tracker,
@@ -69,11 +109,30 @@ impl ScreencopyQueue {
error!("only screencopy with damage can be pushed in the queue");
}
if let Some(cast) = &mut self.cast {
// Update cast output when pushing a new front screencopy.
if self.screencopies.is_empty() {
cast.update_output(screencopy.output());
}
} else {
// First with_damage request, mark this as a screencast.
let output = screencopy.output();
self.cast = Some(ScreencopyCast::new(output));
}
self.screencopies.push(screencopy);
}
pub fn pop(&mut self) -> Screencopy {
self.screencopies.remove(0)
let rv = self.screencopies.remove(0);
// Update cast output (most of the time we expect this to be the same).
if let Some(first) = self.screencopies.first() {
let cast = self.cast.as_mut().unwrap();
cast.update_output(first.output());
}
rv
}
}
@@ -135,6 +194,10 @@ impl ScreencopyManagerState {
self.cleanup_queues();
}
pub fn queues(&self) -> impl Iterator<Item = &ScreencopyQueue> {
self.queues.values()
}
pub fn with_queues_mut(&mut self, mut f: impl FnMut(&mut ScreencopyQueue)) {
for queue in self.queues.values_mut() {
f(queue);