From da64166e82fe6889cf3ce3caf9abcfc24dfbd07f Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Thu, 1 May 2025 18:27:42 +0200 Subject: [PATCH] config: add app-id window criteria --- jay-config/src/_private.rs | 1 + jay-config/src/_private/client.rs | 2 + jay-config/src/window.rs | 4 ++ src/config/handler.rs | 1 + src/criteria/tlm.rs | 12 ++++- src/criteria/tlm/tlm_matchers/tlmm_string.rs | 12 +++++ src/tree/toplevel.rs | 12 ++++- toml-config/src/config.rs | 2 + .../src/config/parsers/window_match.rs | 50 +++++++++++-------- toml-config/src/rules.rs | 2 + toml-spec/spec/spec.generated.json | 8 +++ toml-spec/spec/spec.generated.md | 12 +++++ toml-spec/spec/spec.yaml | 8 +++ 13 files changed, 102 insertions(+), 24 deletions(-) diff --git a/jay-config/src/_private.rs b/jay-config/src/_private.rs index bd9c227a..c25e69f0 100644 --- a/jay-config/src/_private.rs +++ b/jay-config/src/_private.rs @@ -115,4 +115,5 @@ pub enum WindowCriterionIpc { #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] pub enum WindowCriterionStringField { Title, + AppId, } diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 2bfc0633..7a927f95 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -1655,6 +1655,8 @@ impl ConfigClient { } WindowCriterion::Title(t) => string!(t, Title, false), WindowCriterion::TitleRegex(t) => string!(t, Title, true), + WindowCriterion::AppId(t) => string!(t, AppId, false), + WindowCriterion::AppIdRegex(t) => string!(t, AppId, true), }; 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 18c8b35c..7367793e 100644 --- a/jay-config/src/window.rs +++ b/jay-config/src/window.rs @@ -240,6 +240,10 @@ pub enum WindowCriterion<'a> { Title(&'a str), /// Matches the title of the window with a regular expression. TitleRegex(&'a str), + /// Matches the app-id of the window verbatim. + AppId(&'a str), + /// Matches the app-id of the window with a regular expression. + AppIdRegex(&'a str), } impl WindowCriterion<'_> { diff --git a/src/config/handler.rs b/src/config/handler.rs index dae15dc4..47bceb4b 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -1989,6 +1989,7 @@ impl ConfigProxyHandler { }; match *field { WindowCriterionStringField::Title => mgr.title(needle), + WindowCriterionStringField::AppId => mgr.app_id(needle), } } WindowCriterionIpc::Types(t) => mgr.kind(*t), diff --git a/src/criteria/tlm.rs b/src/criteria/tlm.rs index c751d64c..42429f7b 100644 --- a/src/criteria/tlm.rs +++ b/src/criteria/tlm.rs @@ -10,7 +10,9 @@ use { crit_leaf::{CritLeafEvent, CritLeafMatcher}, crit_matchers::critm_constant::CritMatchConstant, tlm::tlm_matchers::{ - tlmm_client::TlmMatchClient, tlmm_kind::TlmMatchKind, tlmm_string::TlmMatchTitle, + tlmm_client::TlmMatchClient, + tlmm_kind::TlmMatchKind, + tlmm_string::{TlmMatchAppId, TlmMatchTitle}, }, }, state::State, @@ -29,6 +31,7 @@ bitflags! { TL_CHANGED_DESTROYED = 1 << 0, TL_CHANGED_NEW = 1 << 1, TL_CHANGED_TITLE = 1 << 2, + TL_CHANGED_APP_ID = 1 << 3, } type TlmFixedRootMatcher = FixedRootMatcher; @@ -48,6 +51,7 @@ pub struct RootMatchers { kinds: TlmRootMatcherMap, clients: CopyHashMap>, title: TlmRootMatcherMap, + app_id: TlmRootMatcherMap, } pub async fn handle_tl_changes(state: Rc) { @@ -143,6 +147,7 @@ impl TlMatcherManager { }; } conditional!(TL_CHANGED_TITLE, title); + conditional!(TL_CHANGED_APP_ID, app_id); false } @@ -210,12 +215,17 @@ impl TlMatcherManager { }; } conditional!(TL_CHANGED_TITLE, title); + conditional!(TL_CHANGED_APP_ID, app_id); } pub fn title(&self, string: CritLiteralOrRegex) -> Rc { self.root(TlmMatchTitle::new(string)) } + pub fn app_id(&self, string: CritLiteralOrRegex) -> Rc { + self.root(TlmMatchAppId::new(string)) + } + pub fn kind(&self, kind: WindowType) -> Rc { self.root(TlmMatchKind::new(kind)) } diff --git a/src/criteria/tlm/tlm_matchers/tlmm_string.rs b/src/criteria/tlm/tlm_matchers/tlmm_string.rs index 3a56edce..967d8032 100644 --- a/src/criteria/tlm/tlm_matchers/tlmm_string.rs +++ b/src/criteria/tlm/tlm_matchers/tlmm_string.rs @@ -9,8 +9,10 @@ use crate::{ pub type TlmMatchString = CritMatchString; pub type TlmMatchTitle = TlmMatchString; +pub type TlmMatchAppId = TlmMatchString; pub struct TitleAccess; +pub struct AppIdAccess; impl StringAccess for TitleAccess { fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool { @@ -21,3 +23,13 @@ impl StringAccess for TitleAccess { &roots.title } } + +impl StringAccess for AppIdAccess { + fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool { + f(&data.app_id.borrow()) + } + + fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap> { + &roots.app_id + } +} diff --git a/src/tree/toplevel.rs b/src/tree/toplevel.rs index 2f53adc7..109d6edb 100644 --- a/src/tree/toplevel.rs +++ b/src/tree/toplevel.rs @@ -3,7 +3,10 @@ use { client::{Client, ClientId}, criteria::{ CritDestroyListener, CritMatcherId, - tlm::{TL_CHANGED_DESTROYED, TL_CHANGED_NEW, TL_CHANGED_TITLE, TlMatcherChange}, + tlm::{ + TL_CHANGED_APP_ID, TL_CHANGED_DESTROYED, TL_CHANGED_NEW, TL_CHANGED_TITLE, + TlMatcherChange, + }, }, ifs::{ ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, @@ -498,11 +501,16 @@ impl ToplevelData { } pub fn set_app_id(&self, app_id: &str) { - *self.app_id.borrow_mut() = app_id.to_string(); + let dst = &mut *self.app_id.borrow_mut(); + if *dst == app_id { + return; + } + *dst = app_id.to_string(); for handle in self.handles.lock().values() { handle.send_app_id(app_id); handle.send_done(); } + self.property_changed(TL_CHANGED_APP_ID) } pub fn set_fullscreen( diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index e7eb3342..a6600007 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -257,6 +257,8 @@ pub struct WindowMatch { pub client: Option, pub title: Option, pub title_regex: Option, + pub app_id: Option, + pub app_id_regex: 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 f4594bd6..2a89c19a 100644 --- a/toml-config/src/config/parsers/window_match.rs +++ b/toml-config/src/config/parsers/window_match.rs @@ -44,27 +44,33 @@ impl Parser for WindowMatchParser<'_> { table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.0, span, table); - let (( - name, - not_val, - all_val, - any_val, - exactly_val, - types_val, - client_val, - title, - title_regex, - ),) = ext.extract((( - opt(str("name")), - opt(val("not")), - opt(arr("all")), - opt(arr("any")), - opt(val("exactly")), - opt(val("types")), - opt(val("client")), - opt(str("title")), - opt(str("title-regex")), - ),))?; + let ( + ( + name, + not_val, + all_val, + any_val, + exactly_val, + types_val, + client_val, + title, + title_regex, + ), + (app_id, app_id_regex), + ) = ext.extract(( + ( + opt(str("name")), + opt(val("not")), + opt(arr("all")), + opt(arr("any")), + opt(val("exactly")), + opt(val("types")), + opt(val("client")), + opt(str("title")), + opt(str("title-regex")), + ), + (opt(str("app-id")), opt(str("app-id-regex"))), + ))?; let mut not = None; if let Some(value) = not_val { not = Some(Box::new(value.parse(&mut WindowMatchParser(self.0))?)); @@ -106,6 +112,8 @@ impl Parser for WindowMatchParser<'_> { }, title: title.despan_into(), title_regex: title_regex.despan_into(), + app_id: app_id.despan_into(), + app_id_regex: app_id_regex.despan_into(), types, client, }) diff --git a/toml-config/src/rules.rs b/toml-config/src/rules.rs index e3abb988..860474fa 100644 --- a/toml-config/src/rules.rs +++ b/toml-config/src/rules.rs @@ -257,6 +257,8 @@ impl Rule for WindowRule { } value!(Title, title); value!(TitleRegex, title_regex); + value!(AppId, app_id); + value!(AppIdRegex, app_id_regex); Some(()) } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 78a37b49..ad17f5b6 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1787,6 +1787,14 @@ "title-regex": { "type": "string", "description": "Matches the title of the window with a regular expression." + }, + "app-id": { + "type": "string", + "description": "Matches the app-id of the window verbatim." + }, + "app-id-regex": { + "type": "string", + "description": "Matches the app-id of the window with a regular expression." } }, "required": [] diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index d9a1023f..8dcf8e8b 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -4022,6 +4022,18 @@ The table has the following fields: The value of this field should be a string. +- `app-id` (optional): + + Matches the app-id of the window verbatim. + + The value of this field should be a string. + +- `app-id-regex` (optional): + + Matches the app-id of the window with a regular expression. + + The value of this field should be a string. + ### `WindowMatchExactly` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 1282eb43..a77ee364 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -3475,6 +3475,14 @@ WindowMatch: kind: string required: false description: Matches the title of the window with a regular expression. + app-id: + kind: string + required: false + description: Matches the app-id of the window verbatim. + app-id-regex: + kind: string + required: false + description: Matches the app-id of the window with a regular expression. WindowMatchExactly: