diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-05-16 11:43:13 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-05-16 12:27:09 +0400 |
| commit | cbfc682f9abc9b4c02ea3c0f63a4403d037f14a4 (patch) | |
| tree | 8075783f9bdae04b3327e61ed613ebd8f4862896 | |
| parent | c64d9e5223a4a19fa718dc5a0b3e74dd6ee3b0e0 (diff) | |
| download | niri-cbfc682f9abc9b4c02ea3c0f63a4403d037f14a4.tar.gz niri-cbfc682f9abc9b4c02ea3c0f63a4403d037f14a4.tar.bz2 niri-cbfc682f9abc9b4c02ea3c0f63a4403d037f14a4.zip | |
Implement at-startup window rule
| -rw-r--r-- | niri-config/src/lib.rs | 6 | ||||
| -rw-r--r-- | src/handlers/xdg_shell.rs | 16 | ||||
| -rw-r--r-- | src/niri.rs | 76 | ||||
| -rw-r--r-- | src/window/mapped.rs | 12 | ||||
| -rw-r--r-- | src/window/mod.rs | 12 | ||||
| -rw-r--r-- | wiki/Configuration:-Window-Rules.md | 18 |
6 files changed, 107 insertions, 33 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 76b33f74..ae3a1d56 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -764,6 +764,8 @@ pub struct Match { pub is_focused: Option<bool>, #[knuffel(property)] pub is_active_in_column: Option<bool>, + #[knuffel(property)] + pub at_startup: Option<bool>, } impl PartialEq for Match { @@ -771,6 +773,7 @@ impl PartialEq for Match { self.is_active == other.is_active && self.is_focused == other.is_focused && self.is_active_in_column == other.is_active_in_column + && self.at_startup == other.at_startup && 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) } @@ -2590,6 +2593,7 @@ mod tests { is_active: None, is_focused: None, is_active_in_column: None, + at_startup: None, }], excludes: vec![ Match { @@ -2598,6 +2602,7 @@ mod tests { is_active: None, is_focused: None, is_active_in_column: None, + at_startup: None, }, Match { app_id: None, @@ -2605,6 +2610,7 @@ mod tests { is_active: Some(true), is_focused: Some(false), is_active_in_column: None, + at_startup: None, }, ], open_on_output: Some("eDP-1".to_owned()), diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 30df1d5f..879bd1b0 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -581,8 +581,11 @@ impl State { }; let config = self.niri.config.borrow(); - let rules = - ResolvedWindowRules::compute(&config.window_rules, WindowRef::Unmapped(unmapped)); + let rules = ResolvedWindowRules::compute( + &config.window_rules, + WindowRef::Unmapped(unmapped), + self.niri.is_at_startup, + ); let Unmapped { window, state } = unmapped; @@ -876,8 +879,11 @@ impl State { let window_rules = &config.window_rules; if let Some(unmapped) = self.niri.unmapped_windows.get_mut(toplevel.wl_surface()) { - let new_rules = - ResolvedWindowRules::compute(window_rules, WindowRef::Unmapped(unmapped)); + let new_rules = ResolvedWindowRules::compute( + window_rules, + WindowRef::Unmapped(unmapped), + self.niri.is_at_startup, + ); if let InitialConfigureState::Configured { rules, .. } = &mut unmapped.state { *rules = new_rules; } @@ -886,7 +892,7 @@ impl State { .layout .find_window_and_output_mut(toplevel.wl_surface()) { - if mapped.recompute_window_rules(window_rules) { + if mapped.recompute_window_rules(window_rules, self.niri.is_at_startup) { drop(config); let output = output.cloned(); let window = mapped.window.clone(); diff --git a/src/niri.rs b/src/niri.rs index f054250c..65a7fac2 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -156,6 +156,9 @@ pub struct Niri { pub start_time: Instant, + /// Whether the at-startup=true window rules are active. + pub is_at_startup: bool, + // Each workspace corresponds to a Space. Each workspace generally has one Output mapped to it, // however it may have none (when there are no outputs connected) or mutiple (when mirroring). pub layout: Layout<Mapped>, @@ -1014,27 +1017,7 @@ impl State { } if window_rules_changed { - let _span = tracy_client::span!("recompute window rules"); - - let window_rules = &self.niri.config.borrow().window_rules; - - for unmapped in self.niri.unmapped_windows.values_mut() { - let new_rules = - ResolvedWindowRules::compute(window_rules, WindowRef::Unmapped(unmapped)); - if let InitialConfigureState::Configured { rules, .. } = &mut unmapped.state { - *rules = new_rules; - } - } - - let mut windows = vec![]; - self.niri.layout.with_windows_mut(|mapped, _| { - if mapped.recompute_window_rules(window_rules) { - windows.push(mapped.window.clone()); - } - }); - for win in windows { - self.niri.layout.update_window(&win, None); - } + self.niri.recompute_window_rules(); } if shaders_changed { @@ -1472,6 +1455,18 @@ impl Niri { }) .unwrap(); + event_loop + .insert_source( + Timer::from_duration(Duration::from_secs(60)), + |_, _, state| { + let _span = tracy_client::span!("startup timeout"); + state.niri.is_at_startup = false; + state.niri.recompute_window_rules(); + TimeoutAction::Drop + }, + ) + .unwrap(); + drop(config_); Self { config, @@ -1483,6 +1478,7 @@ impl Niri { socket_name, display_handle, start_time: Instant::now(), + is_at_startup: true, layout, global_space: Space::default(), @@ -2474,7 +2470,7 @@ impl Niri { 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) { + if mapped.recompute_window_rules_if_needed(window_rules, self.is_at_startup) { windows.push(mapped.window.clone()); if let Some(output) = output { @@ -3901,6 +3897,42 @@ impl Niri { // We don't actually need to queue a redraw because the point is to freeze the screen for a // bit, and even if the delay was zero, we're drawing the same contents anyway. } + + pub fn recompute_window_rules(&mut self) { + let _span = tracy_client::span!("Niri::recompute_window_rules"); + + let changed = { + let window_rules = &self.config.borrow().window_rules; + + for unmapped in self.unmapped_windows.values_mut() { + let new_rules = ResolvedWindowRules::compute( + window_rules, + WindowRef::Unmapped(unmapped), + self.is_at_startup, + ); + if let InitialConfigureState::Configured { rules, .. } = &mut unmapped.state { + *rules = new_rules; + } + } + + let mut windows = vec![]; + self.layout.with_windows_mut(|mapped, _| { + if mapped.recompute_window_rules(window_rules, self.is_at_startup) { + windows.push(mapped.window.clone()); + } + }); + let changed = !windows.is_empty(); + for win in windows { + self.layout.update_window(&win, None); + } + changed + }; + + if changed { + // FIXME: granular. + self.queue_redraw_all(); + } + } } pub struct ClientState { diff --git a/src/window/mapped.rs b/src/window/mapped.rs index bdb62df8..4864d62e 100644 --- a/src/window/mapped.rs +++ b/src/window/mapped.rs @@ -120,10 +120,10 @@ impl Mapped { } /// Recomputes the resolved window rules and returns whether they changed. - pub fn recompute_window_rules(&mut self, rules: &[WindowRule]) -> bool { + pub fn recompute_window_rules(&mut self, rules: &[WindowRule], is_at_startup: bool) -> bool { self.need_to_recompute_rules = false; - let new_rules = ResolvedWindowRules::compute(rules, WindowRef::Mapped(self)); + let new_rules = ResolvedWindowRules::compute(rules, WindowRef::Mapped(self), is_at_startup); if new_rules == self.rules { return false; } @@ -132,12 +132,16 @@ impl Mapped { true } - pub fn recompute_window_rules_if_needed(&mut self, rules: &[WindowRule]) -> bool { + pub fn recompute_window_rules_if_needed( + &mut self, + rules: &[WindowRule], + is_at_startup: bool, + ) -> bool { if !self.need_to_recompute_rules { return false; } - self.recompute_window_rules(rules) + self.recompute_window_rules(rules, is_at_startup) } pub fn is_focused(&self) -> bool { diff --git a/src/window/mod.rs b/src/window/mod.rs index 92b4f793..9bdbd887 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -135,7 +135,7 @@ impl ResolvedWindowRules { } } - pub fn compute(rules: &[WindowRule], window: WindowRef) -> Self { + pub fn compute(rules: &[WindowRule], window: WindowRef, is_at_startup: bool) -> Self { let _span = tracy_client::span!("ResolvedWindowRules::compute"); let mut resolved = ResolvedWindowRules::empty(); @@ -158,7 +158,15 @@ impl ResolvedWindowRules { let mut open_on_workspace = None; for rule in rules { - let matches = |m| window_matches(window, &role, m); + let matches = |m: &Match| { + if let Some(at_startup) = m.at_startup { + if at_startup != is_at_startup { + return false; + } + } + + window_matches(window, &role, m) + }; if !(rule.matches.is_empty() || rule.matches.iter().any(matches)) { continue; diff --git a/wiki/Configuration:-Window-Rules.md b/wiki/Configuration:-Window-Rules.md index d4b50f3b..a86a2cb8 100644 --- a/wiki/Configuration:-Window-Rules.md +++ b/wiki/Configuration:-Window-Rules.md @@ -33,6 +33,7 @@ window-rule { match is-active=true match is-focused=false match is-active-in-column=true + match at-startup=true // Properties that apply once upon window opening. default-column-width { proportion 0.75; } @@ -192,6 +193,23 @@ window-rule { } ``` +#### `at-startup` + +<sup>Since: 0.1.6</sup> + +Can be `true` or `false`. +Matches during the first 60 seconds after starting niri. + +This is useful for properties like `open-on-output` which you may want to apply only right after starting niri. + +``` +// Open windows on the HDMI-A-1 monitor at niri startup, but not afterwards. +window-rule { + match at-startup=true + open-on-output "HDMI-A-1" +} +``` + ### Window Opening Properties These properties apply once, when a window first opens. |
