aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-03-24 09:03:59 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-03-24 10:22:56 +0400
commit493c8dc89072a746795d4e7b94363cfef3e0ee89 (patch)
tree31a384a6272a876d95e271e186e08a92ed88092c
parent8b4a9d68e0ba8093e88d20f3a003f78ef27cac0e (diff)
downloadniri-493c8dc89072a746795d4e7b94363cfef3e0ee89.tar.gz
niri-493c8dc89072a746795d4e7b94363cfef3e0ee89.tar.bz2
niri-493c8dc89072a746795d4e7b94363cfef3e0ee89.zip
Implement block-out-from window rule, fix alpha on window screenshots
-rw-r--r--niri-config/src/lib.rs16
-rw-r--r--niri-visual-tests/src/cases/layout.rs3
-rw-r--r--niri-visual-tests/src/cases/tile.rs2
-rw-r--r--niri-visual-tests/src/cases/window.rs9
-rw-r--r--niri-visual-tests/src/test_window.rs2
-rw-r--r--resources/default-config.kdl34
-rw-r--r--src/backend/tty.rs5
-rw-r--r--src/backend/winit.rs9
-rw-r--r--src/input.rs5
-rw-r--r--src/layout/mod.rs3
-rw-r--r--src/layout/monitor.rs9
-rw-r--r--src/layout/tile.rs11
-rw-r--r--src/layout/workspace.rs13
-rw-r--r--src/niri.rs82
-rw-r--r--src/render_helpers/mod.rs11
-rw-r--r--src/window/mapped.rs45
-rw-r--r--src/window/mod.rs9
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<bool>,
#[knuffel(child, unwrap(argument))]
pub opacity: Option<f32>,
+ #[knuffel(child, unwrap(argument))]
+ pub block_out_from: Option<BlockOutFrom>,
}
// 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<Bind>);
@@ -915,6 +923,8 @@ impl From<niri_ipc::Action> for Action {
#[derive(knuffel::Decode, Debug, Default, PartialEq)]
pub struct DebugConfig {
+ #[knuffel(child, unwrap(argument))]
+ pub preview_render: Option<PreviewRender>,
#[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<Self> {
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<i32, Logical>,
scale: Scale<f64>,
alpha: f32,
+ _target: RenderTarget,
) -> Vec<LayoutElementRenderElement<R>> {
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::<TtyRenderer>(&mut renderer, output, true);
+ let elements =
+ niri.render::<TtyRenderer>(&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::<GlesRenderer>(self.backend.renderer(), output, true);
+ let elements = niri.render::<GlesRenderer>(
+ 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<i32, Logical>,
scale: Scale<f64>,
alpha: f32,
+ target: RenderTarget,
) -> Vec<LayoutElementRenderElement<R>>;
fn request_size(&self, size: Size<i32, Logical>);
@@ -1858,6 +1860,7 @@ mod tests {
_location: Point<i32, Logical>,
_scale: Scale<f64>,
_alpha: f32,
+ _target: RenderTarget,
) -> Vec<LayoutElementRenderElement<R>> {
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<W: LayoutElement> Monitor<W> {
pub fn render_elements<R: NiriRenderer>(
&self,
renderer: &mut R,
+ target: RenderTarget,
) -> Vec<MonitorRenderElement<R>> {
let _span = tracy_client::span!("Monitor::render_elements");
@@ -722,7 +724,7 @@ impl<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Monitor<W> {
};
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<W: LayoutElement> Monitor<W> {
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<W: LayoutElement> Tile<W> {
scale: Scale<f64>,
view_size: Size<i32, Logical>,
focus_ring: bool,
+ target: RenderTarget,
) -> impl Iterator<Item = TileRenderElement<R>> {
let alpha = if self.is_fullscreen {
1.
@@ -333,7 +335,7 @@ impl<W: LayoutElement> Tile<W> {
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<W: LayoutElement> Tile<W> {
scale: Scale<f64>,
view_size: Size<i32, Logical>,
focus_ring: bool,
+ target: RenderTarget,
) -> impl Iterator<Item = TileRenderElement<R>> {
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::<Vec<TileRenderElement<_>>>();
let elem = OffscreenRenderElement::new(
@@ -407,7 +411,8 @@ impl<W: LayoutElement> Tile<W> {
} 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<W: LayoutElement> Workspace<W> {
pub fn render_elements<R: NiriRenderer>(
&self,
renderer: &mut R,
+ target: RenderTarget,
) -> Vec<WorkspaceRenderElement<R>> {
if self.columns.is_empty() {
return vec![];
@@ -1338,8 +1340,15 @@ impl<W: LayoutElement> Workspace<W> {
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<OutputRenderElements<R>> {
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::<GlesRenderer>(renderer, output, true));
+ let elements = elements.get_or_insert_with(|| {
+ self.render::<GlesRenderer>(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::<GlesRenderer>(renderer, &output, true);
+ let elements = self.render::<GlesRenderer>(
+ 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::<GlesRenderer>(renderer, output, true);
+ let elements =
+ self.render::<GlesRenderer>(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::<WaylandSurfaceRenderElement<GlesRenderer>>(
+ 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::<GlesRenderer>(renderer, &output, include_pointer);
+ let elements = self.render::<GlesRenderer>(
+ 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<i32, Physical>,
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<SolidColorBuffer>,
}
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<i32, Logical>,
scale: Scale<f64>,
alpha: f32,
+ target: RenderTarget,
) -> Vec<LayoutElementRenderElement<R>> {
- 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<i32, Logical>) {
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<f32>,
+
+ /// Whether to block out this window from certain render targets.
+ pub block_out_from: Option<BlockOutFrom>,
}
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());