From 8bb8b2a649e10dc1669fa158c8142758dd0238b6 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 1 May 2025 18:31:59 +0200 Subject: [PATCH] config: add floating window criteria --- jay-config/src/_private.rs | 1 + jay-config/src/_private/client.rs | 1 + jay-config/src/window.rs | 2 ++ src/config/handler.rs | 1 + src/criteria/tlm.rs | 35 +++++++++++++++---- src/criteria/tlm/tlm_matchers.rs | 2 +- .../tlm/tlm_matchers/tlmm_floating.rs | 11 ++++++ src/tree/toplevel.rs | 11 ++++-- toml-config/src/config.rs | 1 + .../src/config/parsers/window_match.rs | 11 ++++-- toml-config/src/rules.rs | 2 +- toml-spec/spec/spec.generated.json | 4 +++ toml-spec/spec/spec.generated.md | 6 ++++ toml-spec/spec/spec.yaml | 4 +++ 14 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 src/criteria/tlm/tlm_matchers/tlmm_floating.rs diff --git a/jay-config/src/_private.rs b/jay-config/src/_private.rs index c25e69f0..534531db 100644 --- a/jay-config/src/_private.rs +++ b/jay-config/src/_private.rs @@ -110,6 +110,7 @@ pub enum WindowCriterionIpc { }, Types(WindowType), Client(ClientMatcher), + Floating, } #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 7a927f95..a0288cac 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -1657,6 +1657,7 @@ impl ConfigClient { WindowCriterion::TitleRegex(t) => string!(t, Title, true), WindowCriterion::AppId(t) => string!(t, AppId, false), WindowCriterion::AppIdRegex(t) => string!(t, AppId, true), + WindowCriterion::Floating => WindowCriterionIpc::Floating, }; let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion }); get_response!( diff --git a/jay-config/src/window.rs b/jay-config/src/window.rs index 7367793e..cbada630 100644 --- a/jay-config/src/window.rs +++ b/jay-config/src/window.rs @@ -244,6 +244,8 @@ pub enum WindowCriterion<'a> { AppId(&'a str), /// Matches the app-id of the window with a regular expression. AppIdRegex(&'a str), + /// Matches if the window is floating. + Floating, } impl WindowCriterion<'_> { diff --git a/src/config/handler.rs b/src/config/handler.rs index 47bceb4b..6b03c7f2 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1997,6 +1997,7 @@ impl ConfigProxyHandler { self.state.cl_matcher_manager.rematch_all(&self.state); mgr.client(&self.state, &self.get_client_matcher(*c)?.node) } + WindowCriterionIpc::Floating => mgr.floating(), }; let cached = Rc::new(CachedCriterion { crit: criterion.clone(), diff --git a/src/criteria/tlm.rs b/src/criteria/tlm.rs index 42429f7b..f8c5a88b 100644 --- a/src/criteria/tlm.rs +++ b/src/criteria/tlm.rs @@ -6,11 +6,14 @@ use { CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt, CritUpstreamNode, FixedRootMatcher, RootMatcherMap, clm::ClmUpstreamNode, - crit_graph::{CritMgr, CritTarget, CritTargetOwner, WeakCritTargetOwner}, + crit_graph::{ + CritMgr, CritRoot, CritRootFixed, CritTarget, CritTargetOwner, WeakCritTargetOwner, + }, crit_leaf::{CritLeafEvent, CritLeafMatcher}, crit_matchers::critm_constant::CritMatchConstant, tlm::tlm_matchers::{ tlmm_client::TlmMatchClient, + tlmm_floating::TlmMatchFloating, tlmm_kind::TlmMatchKind, tlmm_string::{TlmMatchAppId, TlmMatchTitle}, }, @@ -23,7 +26,11 @@ use { }, }, jay_config::window::WindowType, - std::rc::{Rc, Weak}, + linearize::static_map, + std::{ + marker::PhantomData, + rc::{Rc, Weak}, + }, }; bitflags! { @@ -32,6 +39,7 @@ bitflags! { TL_CHANGED_NEW = 1 << 1, TL_CHANGED_TITLE = 1 << 2, TL_CHANGED_APP_ID = 1 << 3, + TL_CHANGED_FLOATING = 1 << 4, } type TlmFixedRootMatcher = FixedRootMatcher; @@ -41,6 +49,7 @@ pub struct TlMatcherManager { changes: AsyncQueue>, leaf_events: Rc>>, constant: TlmFixedRootMatcher>, + floating: TlmFixedRootMatcher, matchers: Rc, } @@ -78,8 +87,20 @@ pub type TlmLeafMatcher = CritLeafMatcher; impl TlMatcherManager { pub fn new(ids: &Rc) -> Self { let matchers = Rc::new(RootMatchers::default()); + macro_rules! bool { + ($name:ident) => {{ + static_map! { + v => CritRoot::new( + &matchers, + ids.next(), + CritRootFixed($name(v), PhantomData), + ) + } + }}; + } Self { constant: CritMatchConstant::create(&matchers, ids), + floating: bool!(TlmMatchFloating), changes: Default::default(), leaf_events: Default::default(), ids: ids.clone(), @@ -108,7 +129,6 @@ impl TlMatcherManager { if change.contains(TL_CHANGED_DESTROYED) && data.destroyed.is_not_empty() { return true; } - #[expect(unused_macros)] macro_rules! fixed { ($name:ident) => { if self.$name[false].has_downstream() || self.$name[true].has_downstream() { @@ -138,7 +158,6 @@ impl TlMatcherManager { } }; } - #[expect(unused_macros)] macro_rules! fixed_conditional { ($change:expr, $field:ident) => { if change.contains($change) { @@ -148,6 +167,7 @@ impl TlMatcherManager { } conditional!(TL_CHANGED_TITLE, title); conditional!(TL_CHANGED_APP_ID, app_id); + fixed_conditional!(TL_CHANGED_FLOATING, floating); false } @@ -177,7 +197,6 @@ impl TlMatcherManager { .filter_map(|m| m.upgrade()) }; } - #[expect(unused_macros)] macro_rules! fixed { ($name:ident) => { self.$name[false].handle(data); @@ -206,7 +225,6 @@ impl TlMatcherManager { } }; } - #[expect(unused_macros)] macro_rules! fixed_conditional { ($change:expr, $field:ident) => { if changed.contains($change) { @@ -216,6 +234,7 @@ impl TlMatcherManager { } conditional!(TL_CHANGED_TITLE, title); conditional!(TL_CHANGED_APP_ID, app_id); + fixed_conditional!(TL_CHANGED_FLOATING, floating); } pub fn title(&self, string: CritLiteralOrRegex) -> Rc { @@ -226,6 +245,10 @@ impl TlMatcherManager { self.root(TlmMatchAppId::new(string)) } + pub fn floating(&self) -> Rc { + self.floating[true].clone() + } + pub fn kind(&self, kind: WindowType) -> Rc { self.root(TlmMatchKind::new(kind)) } diff --git a/src/criteria/tlm/tlm_matchers.rs b/src/criteria/tlm/tlm_matchers.rs index d40d6dce..44a5ab0f 100644 --- a/src/criteria/tlm/tlm_matchers.rs +++ b/src/criteria/tlm/tlm_matchers.rs @@ -1,4 +1,3 @@ -#[expect(unused_macros)] macro_rules! fixed_root_criterion { ($ty:ty, $field:ident) => { impl crate::criteria::crit_graph::CritFixedRootCriterionBase @@ -19,5 +18,6 @@ macro_rules! fixed_root_criterion { } pub mod tlmm_client; +pub mod tlmm_floating; pub mod tlmm_kind; pub mod tlmm_string; diff --git a/src/criteria/tlm/tlm_matchers/tlmm_floating.rs b/src/criteria/tlm/tlm_matchers/tlmm_floating.rs new file mode 100644 index 00000000..0386fa0a --- /dev/null +++ b/src/criteria/tlm/tlm_matchers/tlmm_floating.rs @@ -0,0 +1,11 @@ +use crate::{criteria::crit_graph::CritFixedRootCriterion, tree::ToplevelData}; + +pub struct TlmMatchFloating(pub bool); + +fixed_root_criterion!(TlmMatchFloating, floating); + +impl CritFixedRootCriterion for TlmMatchFloating { + fn matches(&self, data: &ToplevelData) -> bool { + data.is_floating.get() + } +} diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 109d6edb..6abfbd68 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -4,8 +4,8 @@ use { criteria::{ CritDestroyListener, CritMatcherId, tlm::{ - TL_CHANGED_APP_ID, TL_CHANGED_DESTROYED, TL_CHANGED_NEW, TL_CHANGED_TITLE, - TlMatcherChange, + TL_CHANGED_APP_ID, TL_CHANGED_DESTROYED, TL_CHANGED_FLOATING, TL_CHANGED_NEW, + TL_CHANGED_TITLE, TlMatcherChange, }, }, ifs::{ @@ -104,7 +104,12 @@ impl ToplevelNode for T { if parent_was_none { data.property_changed(TL_CHANGED_NEW); } - data.is_floating.set(parent.node_is_float()); + let was_floating = data.is_floating.get(); + let is_floating = parent.node_is_float(); + if was_floating != is_floating { + data.property_changed(TL_CHANGED_FLOATING); + } + data.is_floating.set(is_floating); self.tl_set_workspace(&parent.cnode_workspace()); } diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index a6600007..f53f89a1 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -259,6 +259,7 @@ pub struct WindowMatch { pub title_regex: Option, pub app_id: Option, pub app_id_regex: Option, + pub floating: Option, } #[derive(Debug, Clone)] diff --git a/toml-config/src/config/parsers/window_match.rs b/toml-config/src/config/parsers/window_match.rs index 2a89c19a..c3107a46 100644 --- a/toml-config/src/config/parsers/window_match.rs +++ b/toml-config/src/config/parsers/window_match.rs @@ -3,7 +3,7 @@ use { config::{ GenericMatch, MatchExactly, WindowMatch, context::Context, - extractor::{Extractor, ExtractorError, arr, n32, opt, str, val}, + extractor::{Extractor, ExtractorError, arr, bol, n32, opt, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{ client_match::{ClientMatchParser, ClientMatchParserError}, @@ -56,7 +56,7 @@ impl Parser for WindowMatchParser<'_> { title, title_regex, ), - (app_id, app_id_regex), + (app_id, app_id_regex, floating), ) = ext.extract(( ( opt(str("name")), @@ -69,7 +69,11 @@ impl Parser for WindowMatchParser<'_> { opt(str("title")), opt(str("title-regex")), ), - (opt(str("app-id")), opt(str("app-id-regex"))), + ( + opt(str("app-id")), + opt(str("app-id-regex")), + opt(bol("floating")), + ), ))?; let mut not = None; if let Some(value) = not_val { @@ -114,6 +118,7 @@ impl Parser for WindowMatchParser<'_> { title_regex: title_regex.despan_into(), app_id: app_id.despan_into(), app_id_regex: app_id_regex.despan_into(), + floating: floating.despan(), types, client, }) diff --git a/toml-config/src/rules.rs b/toml-config/src/rules.rs index 860474fa..93e59479 100644 --- a/toml-config/src/rules.rs +++ b/toml-config/src/rules.rs @@ -231,7 +231,6 @@ impl Rule for WindowRule { } }; } - #[expect(unused_macros)] macro_rules! bool { ($ty:ident, $field:ident) => { if let Some(value) = &match_.$field { @@ -259,6 +258,7 @@ impl Rule for WindowRule { value!(TitleRegex, title_regex); value!(AppId, app_id); value!(AppIdRegex, app_id_regex); + bool!(Floating, floating); Some(()) } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index ad17f5b6..3b2f52c3 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1795,6 +1795,10 @@ "app-id-regex": { "type": "string", "description": "Matches the app-id of the window with a regular expression." + }, + "floating": { + "type": "boolean", + "description": "Matches if the window is/isn't floating." } }, "required": [] diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 8dcf8e8b..98db444c 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -4034,6 +4034,12 @@ The table has the following fields: The value of this field should be a string. +- `floating` (optional): + + Matches if the window is/isn't floating. + + The value of this field should be a boolean. + ### `WindowMatchExactly` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index a77ee364..76ea9382 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -3483,6 +3483,10 @@ WindowMatch: kind: string required: false description: Matches the app-id of the window with a regular expression. + floating: + kind: boolean + required: false + description: Matches if the window is/isn't floating. WindowMatchExactly: