mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Add is-active window rule matcher
This commit is contained in:
+19
-5
@@ -699,17 +699,21 @@ pub struct WindowRule {
|
||||
pub draw_border_with_background: Option<bool>,
|
||||
}
|
||||
|
||||
// Remember to update the PartialEq impl when adding fields!
|
||||
#[derive(knuffel::Decode, Debug, Default, Clone)]
|
||||
pub struct Match {
|
||||
#[knuffel(property, str)]
|
||||
pub app_id: Option<Regex>,
|
||||
#[knuffel(property, str)]
|
||||
pub title: Option<Regex>,
|
||||
#[knuffel(property)]
|
||||
pub is_active: Option<bool>,
|
||||
}
|
||||
|
||||
impl PartialEq for Match {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.app_id.as_ref().map(Regex::as_str) == other.app_id.as_ref().map(Regex::as_str)
|
||||
self.is_active == other.is_active
|
||||
&& self.app_id.as_ref().map(Regex::as_str) == other.app_id.as_ref().map(Regex::as_str)
|
||||
&& self.title.as_ref().map(Regex::as_str) == other.title.as_ref().map(Regex::as_str)
|
||||
}
|
||||
}
|
||||
@@ -1762,6 +1766,7 @@ mod tests {
|
||||
window-rule {
|
||||
match app-id=".*alacritty"
|
||||
exclude title="~"
|
||||
exclude is-active=true
|
||||
|
||||
open-on-output "eDP-1"
|
||||
open-maximized true
|
||||
@@ -1947,11 +1952,20 @@ mod tests {
|
||||
matches: vec![Match {
|
||||
app_id: Some(Regex::new(".*alacritty").unwrap()),
|
||||
title: None,
|
||||
is_active: None,
|
||||
}],
|
||||
excludes: vec![Match {
|
||||
app_id: None,
|
||||
title: Some(Regex::new("~").unwrap()),
|
||||
}],
|
||||
excludes: vec![
|
||||
Match {
|
||||
app_id: None,
|
||||
title: Some(Regex::new("~").unwrap()),
|
||||
is_active: None,
|
||||
},
|
||||
Match {
|
||||
app_id: None,
|
||||
title: None,
|
||||
is_active: Some(true),
|
||||
},
|
||||
],
|
||||
open_on_output: Some("eDP-1".to_owned()),
|
||||
open_maximized: Some(true),
|
||||
open_fullscreen: Some(false),
|
||||
|
||||
@@ -203,7 +203,7 @@ impl LayoutElement for TestWindow {
|
||||
|
||||
fn set_offscreen_element_id(&self, _id: Option<Id>) {}
|
||||
|
||||
fn set_activated(&self, _active: bool) {}
|
||||
fn set_activated(&mut self, _active: bool) {}
|
||||
|
||||
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
||||
|
||||
|
||||
@@ -364,6 +364,10 @@ animations {
|
||||
// Raw KDL strings are helpful here.
|
||||
exclude app-id=r#"\.unwanted\."#
|
||||
|
||||
// One more way to match is by whether the window is active
|
||||
// (same as when it uses the active border color).
|
||||
match is-active=true
|
||||
|
||||
// Here are the properties that you can set on a window rule.
|
||||
// These properties apply once, when a window first opens.
|
||||
|
||||
|
||||
@@ -745,12 +745,8 @@ impl State {
|
||||
.layout
|
||||
.find_window_and_output_mut(toplevel.wl_surface())
|
||||
{
|
||||
let new_rules = ResolvedWindowRules::compute(window_rules, WindowRef::Mapped(mapped));
|
||||
drop(config);
|
||||
|
||||
if mapped.rules != new_rules {
|
||||
mapped.rules = new_rules;
|
||||
|
||||
if mapped.recompute_window_rules(window_rules) {
|
||||
drop(config);
|
||||
let output = output.cloned();
|
||||
let window = mapped.window.clone();
|
||||
self.niri.layout.update_window(&window);
|
||||
|
||||
+2
-2
@@ -107,7 +107,7 @@ pub trait LayoutElement {
|
||||
fn output_enter(&self, output: &Output);
|
||||
fn output_leave(&self, output: &Output);
|
||||
fn set_offscreen_element_id(&self, id: Option<Id>);
|
||||
fn set_activated(&self, active: bool);
|
||||
fn set_activated(&mut self, active: bool);
|
||||
fn set_bounds(&self, bounds: Size<i32, Logical>);
|
||||
|
||||
fn send_pending_configure(&self);
|
||||
@@ -1893,7 +1893,7 @@ mod tests {
|
||||
|
||||
fn set_offscreen_element_id(&self, _id: Option<Id>) {}
|
||||
|
||||
fn set_activated(&self, _active: bool) {}
|
||||
fn set_activated(&mut self, _active: bool) {}
|
||||
|
||||
fn set_bounds(&self, _bounds: Size<i32, Logical>) {}
|
||||
|
||||
|
||||
@@ -1550,12 +1550,12 @@ impl<W: LayoutElement> Workspace<W> {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn refresh(&self, is_active: bool) {
|
||||
pub fn refresh(&mut self, is_active: bool) {
|
||||
let bounds = self.toplevel_bounds();
|
||||
|
||||
for (col_idx, col) in self.columns.iter().enumerate() {
|
||||
for (tile_idx, tile) in col.tiles.iter().enumerate() {
|
||||
let win = tile.window();
|
||||
for (col_idx, col) in self.columns.iter_mut().enumerate() {
|
||||
for (tile_idx, tile) in col.tiles.iter_mut().enumerate() {
|
||||
let win = tile.window_mut();
|
||||
let active = is_active
|
||||
&& self.active_column_idx == col_idx
|
||||
&& col.active_tile_idx == tile_idx;
|
||||
|
||||
+31
-3
@@ -438,6 +438,7 @@ impl State {
|
||||
self.update_keyboard_focus();
|
||||
self.refresh_pointer_focus();
|
||||
foreign_toplevel::refresh(self);
|
||||
self.niri.refresh_window_rules();
|
||||
self.niri.redraw_queued_outputs(&mut self.backend);
|
||||
|
||||
{
|
||||
@@ -911,9 +912,9 @@ impl State {
|
||||
|
||||
let mut windows = vec![];
|
||||
self.niri.layout.with_windows_mut(|mapped, _| {
|
||||
mapped.rules =
|
||||
ResolvedWindowRules::compute(window_rules, WindowRef::Mapped(mapped));
|
||||
windows.push(mapped.window.clone());
|
||||
if mapped.recompute_window_rules(window_rules) {
|
||||
windows.push(mapped.window.clone());
|
||||
}
|
||||
});
|
||||
for win in windows {
|
||||
self.niri.layout.update_window(&win);
|
||||
@@ -2149,6 +2150,33 @@ impl Niri {
|
||||
self.idle_notifier_state.set_is_inhibited(is_inhibited);
|
||||
}
|
||||
|
||||
pub fn refresh_window_rules(&mut self) {
|
||||
let _span = tracy_client::span!("Niri::refresh_window_rules");
|
||||
|
||||
let config = self.config.borrow();
|
||||
let window_rules = &config.window_rules;
|
||||
|
||||
let mut windows = vec![];
|
||||
let mut outputs = HashSet::new();
|
||||
self.layout.with_windows_mut(|mapped, output| {
|
||||
if mapped.recompute_window_rules_if_needed(window_rules) {
|
||||
windows.push(mapped.window.clone());
|
||||
|
||||
if let Some(output) = output {
|
||||
outputs.insert(output.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
drop(config);
|
||||
|
||||
for win in windows {
|
||||
self.layout.update_window(&win);
|
||||
}
|
||||
for output in outputs {
|
||||
self.queue_redraw(&output);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render<R: NiriRenderer>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
|
||||
+43
-4
@@ -1,5 +1,6 @@
|
||||
use std::cmp::{max, min};
|
||||
|
||||
use niri_config::WindowRule;
|
||||
use smithay::backend::renderer::element::{AsRenderElements as _, Id};
|
||||
use smithay::desktop::space::SpaceElement as _;
|
||||
use smithay::desktop::Window;
|
||||
@@ -11,7 +12,7 @@ use smithay::utils::{Logical, Point, Rectangle, Scale, Size, Transform};
|
||||
use smithay::wayland::compositor::{send_surface_state, with_states};
|
||||
use smithay::wayland::shell::xdg::{SurfaceCachedState, ToplevelSurface};
|
||||
|
||||
use super::ResolvedWindowRules;
|
||||
use super::{ResolvedWindowRules, WindowRef};
|
||||
use crate::layout::{LayoutElement, LayoutElementRenderElement};
|
||||
use crate::niri::WindowOffscreenId;
|
||||
use crate::render_helpers::renderer::NiriRenderer;
|
||||
@@ -22,16 +23,47 @@ pub struct Mapped {
|
||||
|
||||
/// Up-to-date rules.
|
||||
pub rules: ResolvedWindowRules,
|
||||
|
||||
/// Whether the window rules need to be recomputed.
|
||||
///
|
||||
/// This is not used in all cases; for example, app ID and title changes recompute the rules
|
||||
/// immediately, rather than setting this flag.
|
||||
pub need_to_recompute_rules: bool,
|
||||
}
|
||||
|
||||
impl Mapped {
|
||||
pub fn new(window: Window, rules: ResolvedWindowRules) -> Self {
|
||||
Self { window, rules }
|
||||
Self {
|
||||
window,
|
||||
rules,
|
||||
need_to_recompute_rules: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toplevel(&self) -> &ToplevelSurface {
|
||||
self.window.toplevel().expect("no X11 support")
|
||||
}
|
||||
|
||||
/// Recomputes the resolved window rules and returns whether they changed.
|
||||
pub fn recompute_window_rules(&mut self, rules: &[WindowRule]) -> bool {
|
||||
self.need_to_recompute_rules = false;
|
||||
|
||||
let new_rules = ResolvedWindowRules::compute(rules, WindowRef::Mapped(self));
|
||||
if new_rules == self.rules {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.rules = new_rules;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn recompute_window_rules_if_needed(&mut self, rules: &[WindowRule]) -> bool {
|
||||
if !self.need_to_recompute_rules {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.recompute_window_rules(rules)
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutElement for Mapped {
|
||||
@@ -155,8 +187,15 @@ impl LayoutElement for Mapped {
|
||||
data.0.replace(id);
|
||||
}
|
||||
|
||||
fn set_activated(&self, active: bool) {
|
||||
self.window.set_activated(active);
|
||||
fn set_activated(&mut self, active: bool) {
|
||||
let changed = self.toplevel().with_pending_state(|state| {
|
||||
if active {
|
||||
state.states.set(xdg_toplevel::State::Activated)
|
||||
} else {
|
||||
state.states.unset(xdg_toplevel::State::Activated)
|
||||
}
|
||||
});
|
||||
self.need_to_recompute_rules |= changed;
|
||||
}
|
||||
|
||||
fn set_bounds(&self, bounds: Size<i32, Logical>) {
|
||||
|
||||
+20
-1
@@ -1,4 +1,5 @@
|
||||
use niri_config::{Match, WindowRule};
|
||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use smithay::wayland::compositor::with_states;
|
||||
use smithay::wayland::shell::xdg::{
|
||||
ToplevelSurface, XdgToplevelSurfaceData, XdgToplevelSurfaceRoleAttributes,
|
||||
@@ -84,13 +85,18 @@ impl ResolvedWindowRules {
|
||||
|
||||
let toplevel = window.toplevel();
|
||||
with_states(toplevel.wl_surface(), |states| {
|
||||
let role = states
|
||||
let mut role = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
|
||||
// Ensure server_pending like in Smithay's with_pending_state().
|
||||
if role.server_pending.is_none() {
|
||||
role.server_pending = Some(role.current_server_state().clone());
|
||||
}
|
||||
|
||||
let mut open_on_output = None;
|
||||
|
||||
for rule in rules {
|
||||
@@ -150,6 +156,19 @@ impl ResolvedWindowRules {
|
||||
}
|
||||
|
||||
fn window_matches(role: &XdgToplevelSurfaceRoleAttributes, m: &Match) -> bool {
|
||||
// Must be ensured by the caller.
|
||||
let server_pending = role.server_pending.as_ref().unwrap();
|
||||
|
||||
if let Some(is_active) = m.is_active {
|
||||
// Our "is-active" definition corresponds to the window having a pending Activated state.
|
||||
let pending_activated = server_pending
|
||||
.states
|
||||
.contains(xdg_toplevel::State::Activated);
|
||||
if is_active != pending_activated {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(app_id_re) = &m.app_id {
|
||||
let Some(app_id) = &role.app_id else {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user