Protect selected windows from screen capture

Add capture privacy rules for windows, transient children, overview previews and client cursors, with separate capture rendering and deferred-frame invalidation. Keep detached close snapshots out of captures after clients leave the registry.

Validated with release workspace tests, private compositor/EGL regressions, and an isolated release PipeWire capture test.
This commit is contained in:
Samuel Onoja authored and GitHub committed 2026-10-03 10:49:01 +01:00
1 parent bffd538dd4
commit 453ec16583
24 files changed
+1132 -41

No files matched your search

+15 -8
View File
@@ -5,7 +5,7 @@ use std::os::fd::AsFd;
use std::path::Path;
use std::time::{Duration, Instant};
mod capture;
pub(crate) mod capture;
mod lid;
mod mirror;
mod output_power;
@@ -251,6 +251,7 @@ struct DirectOutput {
identity: String,
mirror_source: Option<String>,
mirror_texture: Option<GlesTexture>,
mirror_capture_texture: Option<GlesTexture>,
mirror_canvas: Option<Output>,
mirror_id: smithay::backend::renderer::element::Id,
mirror_commit: CommitCounter,
@@ -1267,13 +1268,17 @@ fn render_output(
primary.sync.wait()?;
}
capture::capture_output(
state,
&mut device.renderer,
&mut output.capture_texture,
&output.output,
&frame,
)?;
if output.mirror_source.is_some() {
capture::capture_mirror(state, &mut device.renderer, &scene_output, &frame, &mut output)?;
} else {
capture::capture_output(
state,
&mut device.renderer,
&mut output.capture_texture,
&output.output,
&frame,
)?;
}
if result.is_empty {
output
@@ -1511,6 +1516,7 @@ fn remove_device(state: &mut Ferese, node: DrmNode) {
let context = device.renderer.context_id().erased();
state.wallpaper.forget_context(&context);
state.render.forget_context(&context);
state.capture_render.forget_context(&context);
for (crtc, mut output) in device.outputs {
cancel_output_timers(&state.loop_handle, &mut output);
if let Some(global) = output.global.take() {
@@ -2027,6 +2033,7 @@ fn create_direct_output(
identity,
mirror_source,
mirror_texture: None,
mirror_capture_texture: None,
mirror_canvas: None,
mirror_id: smithay::backend::renderer::element::Id::new(),
mirror_commit: CommitCounter::default(),
+30 -3
View File
@@ -5,10 +5,10 @@ use smithay::backend::renderer::{Bind, Offscreen, Texture};
use smithay::output::Output;
use crate::Ferese;
use crate::render::{redraw_output, sampled_output_elements};
use crate::render::{capture_output_elements, redraw_output};
use crate::state::FrameScene;
pub(super) fn capture_output(
pub(crate) fn capture_output(
state: &mut Ferese,
renderer: &mut GlesRenderer,
texture: &mut Option<GlesTexture>,
@@ -22,7 +22,7 @@ pub(super) fn capture_output(
// Sample the same scene into an independent target: the displayed
// primary buffer may omit both a scanned-out client and hardware cursor.
let elements = sampled_output_elements(state, renderer, output, include_cursor, scene);
let elements = capture_output_elements(state, renderer, output, include_cursor, scene);
let target = render_capture(renderer, texture, output, &elements)?;
state.process_screencopies(renderer, &target, output, include_cursor);
}
@@ -30,6 +30,33 @@ pub(super) fn capture_output(
Ok(())
}
pub(super) fn capture_mirror(
state: &mut Ferese,
renderer: &mut GlesRenderer,
source: &Output,
scene: &FrameScene,
output: &mut super::DirectOutput,
) -> Result<(), GlesError> {
for include_cursor in [true, false] {
if !state.has_pending_screencopy(&output.output, include_cursor) {
continue;
}
let elements = capture_output_elements(state, renderer, source, include_cursor, scene);
// Never sample mirror_texture: it contains the unfiltered display.
let canvas = output.mirror_canvas.as_ref().expect("mirror display composed");
super::mirror::compose(renderer, &mut output.mirror_capture_texture, canvas, source, &elements)?;
let element = super::mirror::fitted_texture(
output.mirror_capture_texture.as_ref().unwrap().clone(),
&output.output,
smithay::backend::renderer::element::Id::new(),
Default::default(),
);
let target = render_capture(renderer, &mut output.capture_texture, &output.output, &[element])?;
state.process_screencopies(renderer, &target, &output.output, include_cursor);
}
Ok(())
}
pub(super) fn render_capture<'a, E: RenderElement<GlesRenderer>>(
renderer: &mut GlesRenderer,
texture: &'a mut Option<GlesTexture>,
+2 -2
View File
@@ -47,7 +47,7 @@ pub(super) fn elements(
])
}
fn compose(
pub(super) fn compose(
renderer: &mut GlesRenderer,
texture: &mut Option<GlesTexture>,
canvas: &Output,
@@ -69,7 +69,7 @@ fn compose(
Ok(())
}
fn fitted_texture(
pub(super) fn fitted_texture(
texture: GlesTexture,
target: &Output,
id: smithay::backend::renderer::element::Id,