mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
Add is-focused window rule matcher
This commit is contained in:
@@ -708,11 +708,14 @@ pub struct Match {
|
|||||||
pub title: Option<Regex>,
|
pub title: Option<Regex>,
|
||||||
#[knuffel(property)]
|
#[knuffel(property)]
|
||||||
pub is_active: Option<bool>,
|
pub is_active: Option<bool>,
|
||||||
|
#[knuffel(property)]
|
||||||
|
pub is_focused: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Match {
|
impl PartialEq for Match {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.is_active == other.is_active
|
self.is_active == other.is_active
|
||||||
|
&& self.is_focused == other.is_focused
|
||||||
&& self.app_id.as_ref().map(Regex::as_str) == other.app_id.as_ref().map(Regex::as_str)
|
&& 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)
|
&& self.title.as_ref().map(Regex::as_str) == other.title.as_ref().map(Regex::as_str)
|
||||||
}
|
}
|
||||||
@@ -1766,7 +1769,7 @@ mod tests {
|
|||||||
window-rule {
|
window-rule {
|
||||||
match app-id=".*alacritty"
|
match app-id=".*alacritty"
|
||||||
exclude title="~"
|
exclude title="~"
|
||||||
exclude is-active=true
|
exclude is-active=true is-focused=false
|
||||||
|
|
||||||
open-on-output "eDP-1"
|
open-on-output "eDP-1"
|
||||||
open-maximized true
|
open-maximized true
|
||||||
@@ -1953,17 +1956,20 @@ mod tests {
|
|||||||
app_id: Some(Regex::new(".*alacritty").unwrap()),
|
app_id: Some(Regex::new(".*alacritty").unwrap()),
|
||||||
title: None,
|
title: None,
|
||||||
is_active: None,
|
is_active: None,
|
||||||
|
is_focused: None,
|
||||||
}],
|
}],
|
||||||
excludes: vec![
|
excludes: vec![
|
||||||
Match {
|
Match {
|
||||||
app_id: None,
|
app_id: None,
|
||||||
title: Some(Regex::new("~").unwrap()),
|
title: Some(Regex::new("~").unwrap()),
|
||||||
is_active: None,
|
is_active: None,
|
||||||
|
is_focused: None,
|
||||||
},
|
},
|
||||||
Match {
|
Match {
|
||||||
app_id: None,
|
app_id: None,
|
||||||
title: None,
|
title: None,
|
||||||
is_active: Some(true),
|
is_active: Some(true),
|
||||||
|
is_focused: Some(false),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
open_on_output: Some("eDP-1".to_owned()),
|
open_on_output: Some("eDP-1".to_owned()),
|
||||||
|
|||||||
@@ -368,6 +368,13 @@ animations {
|
|||||||
// (same as when it uses the active border color).
|
// (same as when it uses the active border color).
|
||||||
match is-active=true
|
match is-active=true
|
||||||
|
|
||||||
|
// Another way to match is by whether the window has keyboard focus.
|
||||||
|
// This is different from is-active: every workspace on a monitor
|
||||||
|
// has one active window, but only one window can have a keyboard
|
||||||
|
// focus. Also, the keyboard focus can go to a layer-shell surface,
|
||||||
|
// then no window will have the keyboard focus.
|
||||||
|
match is-focused=true
|
||||||
|
|
||||||
// Here are the properties that you can set on a window rule.
|
// Here are the properties that you can set on a window rule.
|
||||||
// These properties apply once, when a window first opens.
|
// These properties apply once, when a window first opens.
|
||||||
|
|
||||||
|
|||||||
+18
@@ -706,6 +706,24 @@ impl State {
|
|||||||
focus
|
focus
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Tell the windows their new focus state for window rule purposes.
|
||||||
|
if let KeyboardFocus::Layout {
|
||||||
|
surface: Some(surface),
|
||||||
|
} = &self.niri.keyboard_focus
|
||||||
|
{
|
||||||
|
if let Some((mapped, _)) = self.niri.layout.find_window_and_output_mut(surface) {
|
||||||
|
mapped.set_is_focused(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let KeyboardFocus::Layout {
|
||||||
|
surface: Some(surface),
|
||||||
|
} = &focus
|
||||||
|
{
|
||||||
|
if let Some((mapped, _)) = self.niri.layout.find_window_and_output_mut(surface) {
|
||||||
|
mapped.set_is_focused(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(grab) = self.niri.popup_grab.as_mut() {
|
if let Some(grab) = self.niri.popup_grab.as_mut() {
|
||||||
if Some(&grab.root) != focus.surface() {
|
if Some(&grab.root) != focus.surface() {
|
||||||
trace!(
|
trace!(
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ pub struct Mapped {
|
|||||||
/// This is not used in all cases; for example, app ID and title changes recompute the rules
|
/// This is not used in all cases; for example, app ID and title changes recompute the rules
|
||||||
/// immediately, rather than setting this flag.
|
/// immediately, rather than setting this flag.
|
||||||
need_to_recompute_rules: bool,
|
need_to_recompute_rules: bool,
|
||||||
|
|
||||||
|
/// Whether this window has the keyboard focus.
|
||||||
|
is_focused: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mapped {
|
impl Mapped {
|
||||||
@@ -37,6 +40,7 @@ impl Mapped {
|
|||||||
window,
|
window,
|
||||||
rules,
|
rules,
|
||||||
need_to_recompute_rules: false,
|
need_to_recompute_rules: false,
|
||||||
|
is_focused: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +68,19 @@ impl Mapped {
|
|||||||
|
|
||||||
self.recompute_window_rules(rules)
|
self.recompute_window_rules(rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_focused(&self) -> bool {
|
||||||
|
self.is_focused
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_is_focused(&mut self, is_focused: bool) {
|
||||||
|
if self.is_focused == is_focused {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.is_focused = is_focused;
|
||||||
|
self.need_to_recompute_rules = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutElement for Mapped {
|
impl LayoutElement for Mapped {
|
||||||
|
|||||||
+18
-5
@@ -61,6 +61,13 @@ impl<'a> WindowRef<'a> {
|
|||||||
WindowRef::Mapped(mapped) => mapped.toplevel(),
|
WindowRef::Mapped(mapped) => mapped.toplevel(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_focused(self) -> bool {
|
||||||
|
match self {
|
||||||
|
WindowRef::Unmapped(_) => false,
|
||||||
|
WindowRef::Mapped(mapped) => mapped.is_focused(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResolvedWindowRules {
|
impl ResolvedWindowRules {
|
||||||
@@ -100,13 +107,13 @@ impl ResolvedWindowRules {
|
|||||||
let mut open_on_output = None;
|
let mut open_on_output = None;
|
||||||
|
|
||||||
for rule in rules {
|
for rule in rules {
|
||||||
if !(rule.matches.is_empty()
|
let matches = |m| window_matches(window, &role, m);
|
||||||
|| rule.matches.iter().any(|m| window_matches(&role, m)))
|
|
||||||
{
|
if !(rule.matches.is_empty() || rule.matches.iter().any(matches)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if rule.excludes.iter().any(|m| window_matches(&role, m)) {
|
if rule.excludes.iter().any(matches) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,10 +162,16 @@ impl ResolvedWindowRules {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_matches(role: &XdgToplevelSurfaceRoleAttributes, m: &Match) -> bool {
|
fn window_matches(window: WindowRef, role: &XdgToplevelSurfaceRoleAttributes, m: &Match) -> bool {
|
||||||
// Must be ensured by the caller.
|
// Must be ensured by the caller.
|
||||||
let server_pending = role.server_pending.as_ref().unwrap();
|
let server_pending = role.server_pending.as_ref().unwrap();
|
||||||
|
|
||||||
|
if let Some(is_focused) = m.is_focused {
|
||||||
|
if window.is_focused() != is_focused {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(is_active) = m.is_active {
|
if let Some(is_active) = m.is_active {
|
||||||
// Our "is-active" definition corresponds to the window having a pending Activated state.
|
// Our "is-active" definition corresponds to the window having a pending Activated state.
|
||||||
let pending_activated = server_pending
|
let pending_activated = server_pending
|
||||||
|
|||||||
Reference in New Issue
Block a user