aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--niri-config/src/lib.rs5
-rw-r--r--src/niri.rs25
-rw-r--r--src/window/mapped.rs17
-rw-r--r--src/window/mod.rs13
-rw-r--r--wiki/Configuration:-Window-Rules.md37
5 files changed, 94 insertions, 3 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index e20b0f49..701f5c5a 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -1210,6 +1210,8 @@ pub struct Match {
#[knuffel(property)]
pub is_floating: Option<bool>,
#[knuffel(property)]
+ pub is_window_cast_target: Option<bool>,
+ #[knuffel(property)]
pub at_startup: Option<bool>,
}
@@ -3948,6 +3950,7 @@ mod tests {
is_focused: None,
is_active_in_column: None,
is_floating: None,
+ is_window_cast_target: None,
at_startup: None,
}],
excludes: vec![
@@ -3958,6 +3961,7 @@ mod tests {
is_focused: None,
is_active_in_column: None,
is_floating: None,
+ is_window_cast_target: None,
at_startup: None,
},
Match {
@@ -3967,6 +3971,7 @@ mod tests {
is_focused: Some(false),
is_active_in_column: None,
is_floating: None,
+ is_window_cast_target: None,
at_startup: None,
},
],
diff --git a/src/niri.rs b/src/niri.rs
index 4dfaf525..b1b9a90c 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -642,13 +642,18 @@ impl State {
self.niri.refresh_idle_inhibit();
self.refresh_pointer_contents();
foreign_toplevel::refresh(self);
+
+ #[cfg(feature = "xdp-gnome-screencast")]
+ self.niri.refresh_mapped_cast_outputs();
+ // Should happen before refresh_window_rules(), but after anything that can start or stop
+ // screencasts.
+ #[cfg(feature = "xdp-gnome-screencast")]
+ self.niri.refresh_mapped_cast_window_rules();
+
self.niri.refresh_window_rules();
self.refresh_ipc_outputs();
self.ipc_refresh_layout();
self.ipc_refresh_keyboard_layout_index();
-
- #[cfg(feature = "xdp-gnome-screencast")]
- self.niri.refresh_mapped_cast_outputs();
}
fn notify_blocker_cleared(&mut self) {
@@ -3257,6 +3262,20 @@ impl Niri {
}
#[cfg(feature = "xdp-gnome-screencast")]
+ pub fn refresh_mapped_cast_window_rules(&mut self) {
+ // O(N^2) but should be fine since there aren't many casts usually.
+ self.layout.with_windows_mut(|mapped, _| {
+ let id = mapped.id().get();
+ // Find regardless of cast.is_active.
+ let value = self
+ .casts
+ .iter()
+ .any(|cast| cast.target == (CastTarget::Window { id }));
+ mapped.set_is_window_cast_target(value);
+ });
+ }
+
+ #[cfg(feature = "xdp-gnome-screencast")]
pub fn refresh_mapped_cast_outputs(&mut self) {
use std::collections::hash_map::Entry;
diff --git a/src/window/mapped.rs b/src/window/mapped.rs
index b5bf8afb..055f2cc5 100644
--- a/src/window/mapped.rs
+++ b/src/window/mapped.rs
@@ -80,6 +80,9 @@ pub struct Mapped {
/// Whether this window is floating.
is_floating: bool,
+ /// Whether this window is a target of a window cast.
+ is_window_cast_target: bool,
+
/// Whether this window should ignore opacity set through window rules.
ignore_opacity_window_rule: bool,
@@ -188,6 +191,7 @@ impl Mapped {
is_focused: false,
is_active_in_column: true,
is_floating: false,
+ is_window_cast_target: false,
ignore_opacity_window_rule: false,
block_out_buffer: RefCell::new(SolidColorBuffer::new((0., 0.), [0., 0., 0., 1.])),
animate_next_configure: false,
@@ -260,6 +264,10 @@ impl Mapped {
self.is_floating
}
+ pub fn is_window_cast_target(&self) -> bool {
+ self.is_window_cast_target
+ }
+
pub fn toggle_ignore_opacity_window_rule(&mut self) {
self.ignore_opacity_window_rule = !self.ignore_opacity_window_rule;
}
@@ -273,6 +281,15 @@ impl Mapped {
self.need_to_recompute_rules = true;
}
+ pub fn set_is_window_cast_target(&mut self, value: bool) {
+ if self.is_window_cast_target == value {
+ return;
+ }
+
+ self.is_window_cast_target = value;
+ self.need_to_recompute_rules = true;
+ }
+
fn render_snapshot(&self, renderer: &mut GlesRenderer) -> LayoutElementRenderSnapshot {
let _span = tracy_client::span!("Mapped::render_snapshot");
diff --git a/src/window/mod.rs b/src/window/mod.rs
index 10aecbfe..41f2182b 100644
--- a/src/window/mod.rs
+++ b/src/window/mod.rs
@@ -146,6 +146,13 @@ impl<'a> WindowRef<'a> {
WindowRef::Mapped(mapped) => mapped.is_floating(),
}
}
+
+ pub fn is_window_cast_target(self) -> bool {
+ match self {
+ WindowRef::Unmapped(_) => false,
+ WindowRef::Mapped(mapped) => mapped.is_window_cast_target(),
+ }
+ }
}
impl ResolvedWindowRules {
@@ -446,5 +453,11 @@ fn window_matches(window: WindowRef, role: &XdgToplevelSurfaceRoleAttributes, m:
}
}
+ if let Some(is_window_cast_target) = m.is_window_cast_target {
+ if window.is_window_cast_target() != is_window_cast_target {
+ return false;
+ }
+ }
+
true
}
diff --git a/wiki/Configuration:-Window-Rules.md b/wiki/Configuration:-Window-Rules.md
index fe4858cf..b294a442 100644
--- a/wiki/Configuration:-Window-Rules.md
+++ b/wiki/Configuration:-Window-Rules.md
@@ -34,6 +34,7 @@ window-rule {
match is-focused=false
match is-active-in-column=true
match is-floating=true
+ match is-window-cast-target=true
match at-startup=true
// Properties that apply once upon window opening.
@@ -239,6 +240,42 @@ window-rule {
}
```
+#### `is-window-cast-target`
+
+<sup>Since: next release</sup>
+
+Can be `true` or `false`.
+Matches `true` for windows that are target of an ongoing window screencast.
+
+```kdl
+// Indicate screencasted windows with red colors.
+window-rule {
+ match is-window-cast-target=true
+
+ focus-ring {
+ active-color "#f38ba8"
+ inactive-color "#7d0d2d"
+ }
+
+ border {
+ inactive-color "#7d0d2d"
+ }
+
+ shadow {
+ color "#7d0d2d70"
+ }
+
+ tab-indicator {
+ active-color "#f38ba8"
+ inactive-color "#7d0d2d"
+ }
+}
+```
+
+Example:
+
+![](https://github.com/user-attachments/assets/375b381e-3a87-4e94-8676-44404971d893)
+
#### `at-startup`
<sup>Since: 0.1.6</sup>