From 493c8dc89072a746795d4e7b94363cfef3e0ee89 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Sun, 24 Mar 2024 09:03:59 +0400 Subject: Implement block-out-from window rule, fix alpha on window screenshots --- niri-config/src/lib.rs | 16 +++++++ niri-visual-tests/src/cases/layout.rs | 3 +- niri-visual-tests/src/cases/tile.rs | 2 + niri-visual-tests/src/cases/window.rs | 9 +++- niri-visual-tests/src/test_window.rs | 2 + resources/default-config.kdl | 34 +++++++++++++++ src/backend/tty.rs | 5 ++- src/backend/winit.rs | 9 +++- src/input.rs | 5 +-- src/layout/mod.rs | 3 ++ src/layout/monitor.rs | 9 ++-- src/layout/tile.rs | 11 +++-- src/layout/workspace.rs | 13 +++++- src/niri.rs | 82 +++++++++++++++++++++++++---------- src/render_helpers/mod.rs | 11 +++++ src/window/mapped.rs | 45 +++++++++++++++---- src/window/mod.rs | 9 +++- 17 files changed, 218 insertions(+), 50 deletions(-) diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 9bf62e13..0bf3fe0d 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -699,6 +699,8 @@ pub struct WindowRule { pub draw_border_with_background: Option, #[knuffel(child, unwrap(argument))] pub opacity: Option, + #[knuffel(child, unwrap(argument))] + pub block_out_from: Option, } // Remember to update the PartialEq impl when adding fields! @@ -723,6 +725,12 @@ impl PartialEq for Match { } } +#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)] +pub enum BlockOutFrom { + Screencast, + ScreenCapture, +} + #[derive(Debug, Default, PartialEq)] pub struct Binds(pub Vec); @@ -915,6 +923,8 @@ impl From for Action { #[derive(knuffel::Decode, Debug, Default, PartialEq)] pub struct DebugConfig { + #[knuffel(child, unwrap(argument))] + pub preview_render: Option, #[knuffel(child)] pub dbus_interfaces_in_non_session_instances: bool, #[knuffel(child)] @@ -931,6 +941,12 @@ pub struct DebugConfig { pub emulate_zero_presentation_time: bool, } +#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)] +pub enum PreviewRender { + Screencast, + ScreenCapture, +} + impl Config { pub fn load(path: &Path) -> miette::Result { let _span = tracy_client::span!("Config::load"); diff --git a/niri-visual-tests/src/cases/layout.rs b/niri-visual-tests/src/cases/layout.rs index e6807228..ead98db2 100644 --- a/niri-visual-tests/src/cases/layout.rs +++ b/niri-visual-tests/src/cases/layout.rs @@ -3,6 +3,7 @@ use std::time::Duration; use niri::layout::workspace::ColumnWidth; use niri::layout::{LayoutElement as _, Options}; +use niri::render_helpers::RenderTarget; use niri::utils::get_monotonic_time; use niri_config::Color; use smithay::backend::renderer::element::RenderElement; @@ -222,7 +223,7 @@ impl TestCase for Layout { self.layout .monitor_for_output(&self.output) .unwrap() - .render_elements(renderer) + .render_elements(renderer, RenderTarget::Output) .into_iter() .map(|elem| Box::new(elem) as _) .collect() diff --git a/niri-visual-tests/src/cases/tile.rs b/niri-visual-tests/src/cases/tile.rs index 2420314f..5b2f7cb8 100644 --- a/niri-visual-tests/src/cases/tile.rs +++ b/niri-visual-tests/src/cases/tile.rs @@ -2,6 +2,7 @@ use std::rc::Rc; use std::time::Duration; use niri::layout::Options; +use niri::render_helpers::RenderTarget; use niri_config::Color; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; @@ -110,6 +111,7 @@ impl TestCase for Tile { Scale::from(1.), size.to_logical(1), true, + RenderTarget::Output, ) .map(|elem| Box::new(elem) as _) .collect() diff --git a/niri-visual-tests/src/cases/window.rs b/niri-visual-tests/src/cases/window.rs index a8855f46..3cd4c0b8 100644 --- a/niri-visual-tests/src/cases/window.rs +++ b/niri-visual-tests/src/cases/window.rs @@ -1,4 +1,5 @@ use niri::layout::LayoutElement; +use niri::render_helpers::RenderTarget; use smithay::backend::renderer::element::RenderElement; use smithay::backend::renderer::gles::GlesRenderer; use smithay::utils::{Logical, Physical, Point, Scale, Size}; @@ -49,7 +50,13 @@ impl TestCase for Window { let location = Point::from(((size.w - win_size.w) / 2, (size.h - win_size.h) / 2)); self.window - .render(renderer, location, Scale::from(1.), 1.) + .render( + renderer, + location, + Scale::from(1.), + 1., + RenderTarget::Output, + ) .into_iter() .map(|elem| Box::new(elem) as _) .collect() diff --git a/niri-visual-tests/src/test_window.rs b/niri-visual-tests/src/test_window.rs index 341f4fbf..6d028db9 100644 --- a/niri-visual-tests/src/test_window.rs +++ b/niri-visual-tests/src/test_window.rs @@ -4,6 +4,7 @@ use std::rc::Rc; use niri::layout::{LayoutElement, LayoutElementRenderElement}; use niri::render_helpers::renderer::NiriRenderer; +use niri::render_helpers::RenderTarget; use niri::window::ResolvedWindowRules; use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; use smithay::backend::renderer::element::{Id, Kind}; @@ -147,6 +148,7 @@ impl LayoutElement for TestWindow { location: Point, scale: Scale, alpha: f32, + _target: RenderTarget, ) -> Vec> { let inner = self.inner.borrow(); diff --git a/resources/default-config.kdl b/resources/default-config.kdl index 0878f213..0bb31697 100644 --- a/resources/default-config.kdl +++ b/resources/default-config.kdl @@ -396,6 +396,22 @@ animations { // The following properties apply dynamically while a window is open. + // You can block out windows from xdg-desktop-portal screencasts. + // They will be replaced with solid black rectangles. + // This can be useful for password managers or messenger windows, etc. + // To preview and set up this rule, check the preview-render option + // in the debug section of the config. + // + // WARNING: the window is NOT blocked out from the screenshot UI. + // If you open the screenshot UI while screencasting, blocked out windows + // WILL BE VISIBLE on the screencast. + block-out-from "screencast" + + // You can also block out the window out of all screen captures, including + // the screenshot UI. This way you avoid accidentally showing the window + // on a screencast when opening the screenshot UI. + block-out-from "screen-capture" + // You can amend the window's minimum and maximum size in logical pixels. // Keep in mind that the window itself always has a final say in its size. // These values instruct niri to never ask the window to be smaller than @@ -437,6 +453,19 @@ window-rule { default-column-width {} } +// Another example: block out two password managers from screencasts. +// (This example rule is commented out with a "/-" in front.) +/-window-rule { + match app-id=r#"^org\.keepassxc\.KeePassXC$"# + match app-id=r#"^org\.gnome\.World\.Secrets$"# + + // Warning: will be visible when opening the screenshot UI. + block-out-from "screencast" + + // Use this instead to block out from all screen captures. + // block-out-from "screen-capture" +} + binds { // Keys consist of modifiers separated by + signs, followed by an XKB key name // in the end. To find an XKB name for a particular key, you may use a program @@ -662,6 +691,11 @@ binds { // Settings for debugging. Not meant for normal use. // These can change or stop working at any point with little notice. debug { + // Render the monitors as if for a screencast or a screen capture. + // Useful for previewing the block-out-from window rule. + // preview-render "screencast" + // preview-render "screen-capture" + // Make niri take over its DBus services even if it's not running as a session. // Useful for testing screen recording changes without having to relogin. // The main niri instance will *not* currently take back the services; so you will diff --git a/src/backend/tty.rs b/src/backend/tty.rs index 83315662..6e923003 100644 --- a/src/backend/tty.rs +++ b/src/backend/tty.rs @@ -59,7 +59,7 @@ use super::RenderResult; use crate::frame_clock::FrameClock; use crate::niri::{Niri, RedrawState, State}; use crate::render_helpers::renderer::AsGlesRenderer; -use crate::render_helpers::shaders; +use crate::render_helpers::{shaders, RenderTarget}; use crate::utils::get_monotonic_time; const SUPPORTED_COLOR_FORMATS: &[Fourcc] = &[Fourcc::Argb8888, Fourcc::Abgr8888]; @@ -1162,7 +1162,8 @@ impl Tty { }; // Render the elements. - let elements = niri.render::(&mut renderer, output, true); + let elements = + niri.render::(&mut renderer, output, true, RenderTarget::Output); // Hand them over to the DRM. let drm_compositor = &mut surface.compositor; diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 2f342b41..d23ba649 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -19,7 +19,7 @@ use smithay::reexports::winit::window::WindowBuilder; use super::RenderResult; use crate::niri::{Niri, RedrawState, State}; -use crate::render_helpers::shaders; +use crate::render_helpers::{shaders, RenderTarget}; use crate::utils::get_monotonic_time; pub struct Winit { @@ -147,7 +147,12 @@ impl Winit { let _span = tracy_client::span!("Winit::render"); // Render the elements. - let elements = niri.render::(self.backend.renderer(), output, true); + let elements = niri.render::( + self.backend.renderer(), + output, + true, + RenderTarget::Output, + ); // Hand them over to winit. self.backend.bind().unwrap(); diff --git a/src/input.rs b/src/input.rs index a47f9151..49aaf3f9 100644 --- a/src/input.rs +++ b/src/input.rs @@ -416,10 +416,7 @@ impl State { let active = self.niri.layout.active_window(); if let Some((mapped, output)) = active { self.backend.with_primary_renderer(|renderer| { - if let Err(err) = - self.niri - .screenshot_window(renderer, output, &mapped.window) - { + if let Err(err) = self.niri.screenshot_window(renderer, output, &mapped) { warn!("error taking screenshot: {err:?}"); } }); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index bbca41c9..611f138f 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -48,6 +48,7 @@ pub use self::monitor::MonitorRenderElement; use self::workspace::{compute_working_area, Column, ColumnWidth, OutputId, Workspace}; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; +use crate::render_helpers::RenderTarget; use crate::utils::output_size; use crate::window::ResolvedWindowRules; @@ -96,6 +97,7 @@ pub trait LayoutElement { location: Point, scale: Scale, alpha: f32, + target: RenderTarget, ) -> Vec>; fn request_size(&self, size: Size); @@ -1858,6 +1860,7 @@ mod tests { _location: Point, _scale: Scale, _alpha: f32, + _target: RenderTarget, ) -> Vec> { vec![] } diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs index fbea52cd..6e8cb36d 100644 --- a/src/layout/monitor.rs +++ b/src/layout/monitor.rs @@ -16,6 +16,7 @@ use super::workspace::{ use super::{LayoutElement, Options}; use crate::animation::Animation; use crate::render_helpers::renderer::NiriRenderer; +use crate::render_helpers::RenderTarget; use crate::rubber_band::RubberBand; use crate::swipe_tracker::SwipeTracker; use crate::utils::output_size; @@ -700,6 +701,7 @@ impl Monitor { pub fn render_elements( &self, renderer: &mut R, + target: RenderTarget, ) -> Vec> { let _span = tracy_client::span!("Monitor::render_elements"); @@ -722,7 +724,7 @@ impl Monitor { let after_idx = after_idx as usize; let after = if after_idx < self.workspaces.len() { - let after = self.workspaces[after_idx].render_elements(renderer); + let after = self.workspaces[after_idx].render_elements(renderer, target); let after = after.into_iter().filter_map(|elem| { Some(RelocateRenderElement::from_element( CropRenderElement::from_element( @@ -752,7 +754,7 @@ impl Monitor { }; let before_idx = before_idx as usize; - let before = self.workspaces[before_idx].render_elements(renderer); + let before = self.workspaces[before_idx].render_elements(renderer, target); let before = before.into_iter().filter_map(|elem| { Some(RelocateRenderElement::from_element( CropRenderElement::from_element( @@ -770,7 +772,8 @@ impl Monitor { before.chain(after.into_iter().flatten()).collect() } None => { - let elements = self.workspaces[self.active_workspace_idx].render_elements(renderer); + let elements = + self.workspaces[self.active_workspace_idx].render_elements(renderer, target); elements .into_iter() .filter_map(|elem| { diff --git a/src/layout/tile.rs b/src/layout/tile.rs index faa17840..3d2da48c 100644 --- a/src/layout/tile.rs +++ b/src/layout/tile.rs @@ -13,6 +13,7 @@ use crate::animation::Animation; use crate::niri_render_elements; use crate::render_helpers::offscreen::OffscreenRenderElement; use crate::render_helpers::renderer::NiriRenderer; +use crate::render_helpers::RenderTarget; /// Toplevel window with decorations. #[derive(Debug)] @@ -324,6 +325,7 @@ impl Tile { scale: Scale, view_size: Size, focus_ring: bool, + target: RenderTarget, ) -> impl Iterator> { let alpha = if self.is_fullscreen { 1. @@ -333,7 +335,7 @@ impl Tile { let rv = self .window - .render(renderer, location + self.window_loc(), scale, alpha) + .render(renderer, location + self.window_loc(), scale, alpha, target) .into_iter() .map(Into::into); @@ -376,10 +378,12 @@ impl Tile { scale: Scale, view_size: Size, focus_ring: bool, + target: RenderTarget, ) -> impl Iterator> { if let Some(anim) = &self.open_animation { let renderer = renderer.as_gles_renderer(); - let elements = self.render_inner(renderer, location, scale, view_size, focus_ring); + let elements = + self.render_inner(renderer, location, scale, view_size, focus_ring, target); let elements = elements.collect::>>(); let elem = OffscreenRenderElement::new( @@ -407,7 +411,8 @@ impl Tile { } else { self.window().set_offscreen_element_id(None); - let elements = self.render_inner(renderer, location, scale, view_size, focus_ring); + let elements = + self.render_inner(renderer, location, scale, view_size, focus_ring, target); None.into_iter().chain(Some(elements).into_iter().flatten()) } } diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 64be3bcc..7ce71c07 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -17,6 +17,7 @@ use super::{LayoutElement, Options}; use crate::animation::Animation; use crate::niri_render_elements; use crate::render_helpers::renderer::NiriRenderer; +use crate::render_helpers::RenderTarget; use crate::swipe_tracker::SwipeTracker; use crate::utils::id::IdCounter; use crate::utils::output_size; @@ -1316,6 +1317,7 @@ impl Workspace { pub fn render_elements( &self, renderer: &mut R, + target: RenderTarget, ) -> Vec> { if self.columns.is_empty() { return vec![]; @@ -1338,8 +1340,15 @@ impl Workspace { first = false; rv.extend( - tile.render(renderer, tile_pos, output_scale, self.view_size, focus_ring) - .map(Into::into), + tile.render( + renderer, + tile_pos, + output_scale, + self.view_size, + focus_ring, + target, + ) + .map(Into::into), ); } diff --git a/src/niri.rs b/src/niri.rs index 363c2ae6..88326119 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -11,7 +11,7 @@ use std::{env, mem, thread}; use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as KdeDecorationsMode; use anyhow::{ensure, Context}; use calloop::futures::Scheduler; -use niri_config::{Config, Key, Modifiers, TrackLayout}; +use niri_config::{Config, Key, Modifiers, PreviewRender, TrackLayout}; use smithay::backend::allocator::Fourcc; use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement; use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; @@ -22,8 +22,8 @@ use smithay::backend::renderer::element::utils::{ select_dmabuf_feedback, Relocate, RelocateRenderElement, }; use smithay::backend::renderer::element::{ - default_primary_scanout_output_compare, AsRenderElements, Id, Kind, PrimaryScanoutOutput, - RenderElementStates, + default_primary_scanout_output_compare, AsRenderElements, Element as _, Id, Kind, + PrimaryScanoutOutput, RenderElementStates, }; use smithay::backend::renderer::gles::GlesRenderer; use smithay::desktop::utils::{ @@ -101,13 +101,13 @@ use crate::input::{ apply_libinput_settings, mods_with_finger_scroll_binds, mods_with_wheel_binds, TabletData, }; use crate::ipc::server::IpcServer; -use crate::layout::{Layout, MonitorRenderElement}; +use crate::layout::{Layout, LayoutElement as _, MonitorRenderElement}; 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}; use crate::render_helpers::renderer::NiriRenderer; -use crate::render_helpers::{render_to_shm, render_to_texture, render_to_vec}; +use crate::render_helpers::{render_to_shm, render_to_texture, render_to_vec, RenderTarget}; use crate::scroll_tracker::ScrollTracker; use crate::ui::config_error_notification::ConfigErrorNotification; use crate::ui::exit_confirm_dialog::ExitConfirmDialog; @@ -2230,9 +2230,19 @@ impl Niri { renderer: &mut R, output: &Output, include_pointer: bool, + mut target: RenderTarget, ) -> Vec> { let _span = tracy_client::span!("Niri::render"); + if target == RenderTarget::Output { + if let Some(preview) = self.config.borrow().debug.preview_render { + target = match preview { + PreviewRender::Screencast => RenderTarget::Screencast, + PreviewRender::ScreenCapture => RenderTarget::ScreenCapture, + }; + } + } + let output_scale = Scale::from(output.current_scale().fractional_scale()); // The pointer goes on the top. @@ -2315,7 +2325,7 @@ impl Niri { // Get monitor elements. let mon = self.layout.monitor_for_output(output).unwrap(); - let monitor_elements = mon.render_elements(renderer); + let monitor_elements = mon.render_elements(renderer, target); // Get layer-shell elements. let layer_map = layer_map_for_output(output); @@ -2970,8 +2980,9 @@ impl Niri { let dmabuf = cast.dmabufs.borrow()[&fd].clone(); // FIXME: Hidden / embedded / metadata cursor - let elements = elements - .get_or_insert_with(|| self.render::(renderer, output, true)); + let elements = elements.get_or_insert_with(|| { + self.render::(renderer, output, true, RenderTarget::Screencast) + }); let elements = elements.iter().rev(); if let Err(err) = @@ -3007,7 +3018,12 @@ impl Niri { backend .with_primary_renderer(move |renderer| { let elements = self - .render(renderer, &output, screencopy.overlay_cursor()) + .render( + renderer, + &output, + screencopy.overlay_cursor(), + RenderTarget::ScreenCapture, + ) .into_iter() .rev(); @@ -3088,7 +3104,12 @@ impl Niri { let size = transform.transform_size(size); let scale = Scale::from(output.current_scale().fractional_scale()); - let elements = self.render::(renderer, &output, true); + let elements = self.render::( + renderer, + &output, + true, + RenderTarget::ScreenCapture, + ); let elements = elements.iter().rev(); let res = render_to_texture( @@ -3126,7 +3147,8 @@ impl Niri { let size = transform.transform_size(size); let scale = Scale::from(output.current_scale().fractional_scale()); - let elements = self.render::(renderer, output, true); + let elements = + self.render::(renderer, output, true, RenderTarget::ScreenCapture); let elements = elements.iter().rev(); let pixels = render_to_vec( renderer, @@ -3145,32 +3167,43 @@ impl Niri { &self, renderer: &mut GlesRenderer, output: &Output, - window: &Window, + mapped: &Mapped, ) -> anyhow::Result<()> { let _span = tracy_client::span!("Niri::screenshot_window"); 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 buf_pos = Point::from((0, 0)) - bbox.loc; + let alpha = if mapped.is_fullscreen() { + 1. + } else { + mapped.rules().opacity.unwrap_or(1.).clamp(0., 1.) + }; // FIXME: pointer. - let elements = window.render_elements::>( + let elements = mapped.render( renderer, - buf_pos.to_physical_precise_ceil(scale), + mapped.window.geometry().loc, scale, - 1., + alpha, + RenderTarget::ScreenCapture, ); - let elements = elements.iter().rev(); + 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) + }); let pixels = render_to_vec( renderer, - size, + geo.size, scale, Transform::Normal, Fourcc::Abgr8888, elements, )?; - self.save_screenshot(size, pixels) + self.save_screenshot(geo.size, pixels) .context("error saving screenshot") } @@ -3265,7 +3298,12 @@ impl Niri { let transform = output.current_transform(); let size = transform.transform_size(size); - let elements = self.render::(renderer, &output, include_pointer); + let elements = self.render::( + renderer, + &output, + include_pointer, + RenderTarget::ScreenCapture, + ); let elements = elements.iter().rev(); let pixels = render_to_vec( renderer, diff --git a/src/render_helpers/mod.rs b/src/render_helpers/mod.rs index 02ef2853..f0d61fbe 100644 --- a/src/render_helpers/mod.rs +++ b/src/render_helpers/mod.rs @@ -19,6 +19,17 @@ pub mod render_elements; pub mod renderer; pub mod shaders; +/// What we're rendering for. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RenderTarget { + /// Rendering to display on screen. + Output, + /// Rendering for a screencast. + Screencast, + /// Rendering for any other screen capture. + ScreenCapture, +} + pub fn render_to_texture( renderer: &mut GlesRenderer, size: Size, diff --git a/src/window/mapped.rs b/src/window/mapped.rs index 52b9ac92..8a4b2473 100644 --- a/src/window/mapped.rs +++ b/src/window/mapped.rs @@ -1,7 +1,9 @@ +use std::cell::RefCell; use std::cmp::{max, min}; -use niri_config::WindowRule; -use smithay::backend::renderer::element::{AsRenderElements as _, Id}; +use niri_config::{BlockOutFrom, WindowRule}; +use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; +use smithay::backend::renderer::element::{AsRenderElements as _, Id, Kind}; use smithay::desktop::space::SpaceElement as _; use smithay::desktop::Window; use smithay::output::Output; @@ -16,6 +18,7 @@ use super::{ResolvedWindowRules, WindowRef}; use crate::layout::{LayoutElement, LayoutElementRenderElement}; use crate::niri::WindowOffscreenId; use crate::render_helpers::renderer::NiriRenderer; +use crate::render_helpers::RenderTarget; #[derive(Debug)] pub struct Mapped { @@ -32,6 +35,9 @@ pub struct Mapped { /// Whether this window has the keyboard focus. is_focused: bool, + + /// Buffer to draw instead of the window when it should be blocked out. + block_out_buffer: RefCell, } impl Mapped { @@ -41,6 +47,7 @@ impl Mapped { rules, need_to_recompute_rules: false, is_focused: false, + block_out_buffer: RefCell::new(SolidColorBuffer::new((0, 0), [0., 0., 0., 1.])), } } @@ -109,14 +116,34 @@ impl LayoutElement for Mapped { location: Point, scale: Scale, alpha: f32, + target: RenderTarget, ) -> Vec> { - let buf_pos = location - self.window.geometry().loc; - self.window.render_elements( - renderer, - buf_pos.to_physical_precise_round(scale), - scale, - alpha, - ) + let block_out = match self.rules.block_out_from { + None => false, + Some(BlockOutFrom::Screencast) => target == RenderTarget::Screencast, + Some(BlockOutFrom::ScreenCapture) => target != RenderTarget::Output, + }; + + if block_out { + let mut buffer = self.block_out_buffer.borrow_mut(); + buffer.resize(self.window.geometry().size); + let elem = SolidColorRenderElement::from_buffer( + &buffer, + location.to_physical_precise_round(scale), + scale, + alpha, + Kind::Unspecified, + ); + vec![elem.into()] + } else { + let buf_pos = location - self.window.geometry().loc; + self.window.render_elements( + renderer, + buf_pos.to_physical_precise_round(scale), + scale, + alpha, + ) + } } fn request_size(&self, size: Size) { diff --git a/src/window/mod.rs b/src/window/mod.rs index ddf712b0..3c46e9e5 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -1,4 +1,4 @@ -use niri_config::{Match, WindowRule}; +use niri_config::{BlockOutFrom, Match, WindowRule}; use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel; use smithay::wayland::compositor::with_states; use smithay::wayland::shell::xdg::{ @@ -55,6 +55,9 @@ pub struct ResolvedWindowRules { /// Extra opacity to draw this window with. pub opacity: Option, + + /// Whether to block out this window from certain render targets. + pub block_out_from: Option, } impl<'a> WindowRef<'a> { @@ -86,6 +89,7 @@ impl ResolvedWindowRules { max_height: None, draw_border_with_background: None, opacity: None, + block_out_from: None, } } @@ -160,6 +164,9 @@ impl ResolvedWindowRules { if let Some(x) = rule.opacity { resolved.opacity = Some(x); } + if let Some(x) = rule.block_out_from { + resolved.block_out_from = Some(x); + } } resolved.open_on_output = open_on_output.map(|x| x.to_owned()); -- cgit