mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
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:
+62
-32
@@ -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,42 +811,73 @@ impl State {
|
||||
let mut events = Vec::new();
|
||||
let mut seen = HashSet::new();
|
||||
|
||||
// Check pending dynamic casts.
|
||||
for pending in &self.niri.casting.pending_dynamic_casts {
|
||||
let stream_id = pending.stream_id.get();
|
||||
seen.insert(stream_id);
|
||||
// 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.
|
||||
if !state.casts.contains_key(&stream_id) {
|
||||
let cast = niri_ipc::Cast {
|
||||
session_id: pending.session_id.get(),
|
||||
stream_id,
|
||||
target: niri_ipc::CastTarget::Nothing {},
|
||||
is_dynamic_target: true,
|
||||
is_active: false,
|
||||
};
|
||||
events.push(Event::CastStartedOrChanged { cast });
|
||||
// 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(),
|
||||
stream_id,
|
||||
target: niri_ipc::CastTarget::Nothing {},
|
||||
is_dynamic_target: true,
|
||||
is_active: false,
|
||||
};
|
||||
events.push(Event::CastStartedOrChanged { cast });
|
||||
}
|
||||
}
|
||||
|
||||
// Check active casts.
|
||||
for cast in &self.niri.casting.casts {
|
||||
let stream_id = cast.stream_id.get();
|
||||
seen.insert(stream_id);
|
||||
|
||||
if state.casts.get(&stream_id).is_none_or(|existing| {
|
||||
// Only these properties can change.
|
||||
existing.is_active != cast.is_active() || !cast.target.matches(&existing.target)
|
||||
}) {
|
||||
let cast = niri_ipc::Cast {
|
||||
session_id: cast.session_id.get(),
|
||||
stream_id,
|
||||
target: cast.target.make_ipc(),
|
||||
is_dynamic_target: cast.dynamic_target,
|
||||
is_active: cast.is_active(),
|
||||
};
|
||||
events.push(Event::CastStartedOrChanged { cast });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check active casts.
|
||||
for cast in &self.niri.casting.casts {
|
||||
let stream_id = cast.stream_id.get();
|
||||
seen.insert(stream_id);
|
||||
// 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 these properties can change.
|
||||
existing.is_active != cast.is_active() || !cast.target.matches(&existing.target)
|
||||
}) {
|
||||
let cast = niri_ipc::Cast {
|
||||
session_id: cast.session_id.get(),
|
||||
stream_id,
|
||||
target: cast.target.make_ipc(),
|
||||
is_dynamic_target: cast.dynamic_target,
|
||||
is_active: cast.is_active(),
|
||||
};
|
||||
events.push(Event::CastStartedOrChanged { cast });
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user