1
0
Fork 0
forked from wry/wry

config: add app-id window criteria

This commit is contained in:
Julian Orth 2025-05-01 18:27:42 +02:00
parent 6ef7655dbd
commit da64166e82
13 changed files with 102 additions and 24 deletions

View file

@ -115,4 +115,5 @@ pub enum WindowCriterionIpc {
#[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)]
pub enum WindowCriterionStringField { pub enum WindowCriterionStringField {
Title, Title,
AppId,
} }

View file

@ -1655,6 +1655,8 @@ impl ConfigClient {
} }
WindowCriterion::Title(t) => string!(t, Title, false), WindowCriterion::Title(t) => string!(t, Title, false),
WindowCriterion::TitleRegex(t) => string!(t, Title, true), 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 }); let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
get_response!( get_response!(

View file

@ -240,6 +240,10 @@ pub enum WindowCriterion<'a> {
Title(&'a str), Title(&'a str),
/// Matches the title of the window with a regular expression. /// Matches the title of the window with a regular expression.
TitleRegex(&'a str), 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<'_> { impl WindowCriterion<'_> {

View file

@ -1989,6 +1989,7 @@ impl ConfigProxyHandler {
}; };
match *field { match *field {
WindowCriterionStringField::Title => mgr.title(needle), WindowCriterionStringField::Title => mgr.title(needle),
WindowCriterionStringField::AppId => mgr.app_id(needle),
} }
} }
WindowCriterionIpc::Types(t) => mgr.kind(*t), WindowCriterionIpc::Types(t) => mgr.kind(*t),

View file

@ -10,7 +10,9 @@ use {
crit_leaf::{CritLeafEvent, CritLeafMatcher}, crit_leaf::{CritLeafEvent, CritLeafMatcher},
crit_matchers::critm_constant::CritMatchConstant, crit_matchers::critm_constant::CritMatchConstant,
tlm::tlm_matchers::{ tlm::tlm_matchers::{
tlmm_client::TlmMatchClient, tlmm_kind::TlmMatchKind, tlmm_string::TlmMatchTitle, tlmm_client::TlmMatchClient,
tlmm_kind::TlmMatchKind,
tlmm_string::{TlmMatchAppId, TlmMatchTitle},
}, },
}, },
state::State, state::State,
@ -29,6 +31,7 @@ bitflags! {
TL_CHANGED_DESTROYED = 1 << 0, TL_CHANGED_DESTROYED = 1 << 0,
TL_CHANGED_NEW = 1 << 1, TL_CHANGED_NEW = 1 << 1,
TL_CHANGED_TITLE = 1 << 2, TL_CHANGED_TITLE = 1 << 2,
TL_CHANGED_APP_ID = 1 << 3,
} }
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>; type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
@ -48,6 +51,7 @@ pub struct RootMatchers {
kinds: TlmRootMatcherMap<TlmMatchKind>, kinds: TlmRootMatcherMap<TlmMatchKind>,
clients: CopyHashMap<CritMatcherId, Weak<TlmMatchClient>>, clients: CopyHashMap<CritMatcherId, Weak<TlmMatchClient>>,
title: TlmRootMatcherMap<TlmMatchTitle>, title: TlmRootMatcherMap<TlmMatchTitle>,
app_id: TlmRootMatcherMap<TlmMatchAppId>,
} }
pub async fn handle_tl_changes(state: Rc<State>) { pub async fn handle_tl_changes(state: Rc<State>) {
@ -143,6 +147,7 @@ impl TlMatcherManager {
}; };
} }
conditional!(TL_CHANGED_TITLE, title); conditional!(TL_CHANGED_TITLE, title);
conditional!(TL_CHANGED_APP_ID, app_id);
false false
} }
@ -210,12 +215,17 @@ impl TlMatcherManager {
}; };
} }
conditional!(TL_CHANGED_TITLE, title); conditional!(TL_CHANGED_TITLE, title);
conditional!(TL_CHANGED_APP_ID, app_id);
} }
pub fn title(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> { pub fn title(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
self.root(TlmMatchTitle::new(string)) self.root(TlmMatchTitle::new(string))
} }
pub fn app_id(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
self.root(TlmMatchAppId::new(string))
}
pub fn kind(&self, kind: WindowType) -> Rc<TlmUpstreamNode> { pub fn kind(&self, kind: WindowType) -> Rc<TlmUpstreamNode> {
self.root(TlmMatchKind::new(kind)) self.root(TlmMatchKind::new(kind))
} }

View file

@ -9,8 +9,10 @@ use crate::{
pub type TlmMatchString<T> = CritMatchString<ToplevelData, T>; pub type TlmMatchString<T> = CritMatchString<ToplevelData, T>;
pub type TlmMatchTitle = TlmMatchString<TitleAccess>; pub type TlmMatchTitle = TlmMatchString<TitleAccess>;
pub type TlmMatchAppId = TlmMatchString<AppIdAccess>;
pub struct TitleAccess; pub struct TitleAccess;
pub struct AppIdAccess;
impl StringAccess<ToplevelData> for TitleAccess { impl StringAccess<ToplevelData> for TitleAccess {
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool { fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
@ -21,3 +23,13 @@ impl StringAccess<ToplevelData> for TitleAccess {
&roots.title &roots.title
} }
} }
impl StringAccess<ToplevelData> for AppIdAccess {
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
f(&data.app_id.borrow())
}
fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap<TlmMatchString<Self>> {
&roots.app_id
}
}

View file

@ -3,7 +3,10 @@ use {
client::{Client, ClientId}, client::{Client, ClientId},
criteria::{ criteria::{
CritDestroyListener, CritMatcherId, 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::{ ifs::{
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1, ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
@ -498,11 +501,16 @@ impl ToplevelData {
} }
pub fn set_app_id(&self, app_id: &str) { 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() { for handle in self.handles.lock().values() {
handle.send_app_id(app_id); handle.send_app_id(app_id);
handle.send_done(); handle.send_done();
} }
self.property_changed(TL_CHANGED_APP_ID)
} }
pub fn set_fullscreen( pub fn set_fullscreen(

View file

@ -257,6 +257,8 @@ pub struct WindowMatch {
pub client: Option<ClientMatch>, pub client: Option<ClientMatch>,
pub title: Option<String>, pub title: Option<String>,
pub title_regex: Option<String>, pub title_regex: Option<String>,
pub app_id: Option<String>,
pub app_id_regex: Option<String>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -44,27 +44,33 @@ impl Parser for WindowMatchParser<'_> {
table: &IndexMap<Spanned<String>, Spanned<Value>>, table: &IndexMap<Spanned<String>, Spanned<Value>>,
) -> ParseResult<Self> { ) -> ParseResult<Self> {
let mut ext = Extractor::new(self.0, span, table); let mut ext = Extractor::new(self.0, span, table);
let (( let (
name, (
not_val, name,
all_val, not_val,
any_val, all_val,
exactly_val, any_val,
types_val, exactly_val,
client_val, types_val,
title, client_val,
title_regex, title,
),) = ext.extract((( title_regex,
opt(str("name")), ),
opt(val("not")), (app_id, app_id_regex),
opt(arr("all")), ) = ext.extract((
opt(arr("any")), (
opt(val("exactly")), opt(str("name")),
opt(val("types")), opt(val("not")),
opt(val("client")), opt(arr("all")),
opt(str("title")), opt(arr("any")),
opt(str("title-regex")), 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; let mut not = None;
if let Some(value) = not_val { if let Some(value) = not_val {
not = Some(Box::new(value.parse(&mut WindowMatchParser(self.0))?)); not = Some(Box::new(value.parse(&mut WindowMatchParser(self.0))?));
@ -106,6 +112,8 @@ impl Parser for WindowMatchParser<'_> {
}, },
title: title.despan_into(), title: title.despan_into(),
title_regex: title_regex.despan_into(), title_regex: title_regex.despan_into(),
app_id: app_id.despan_into(),
app_id_regex: app_id_regex.despan_into(),
types, types,
client, client,
}) })

View file

@ -257,6 +257,8 @@ impl Rule for WindowRule {
} }
value!(Title, title); value!(Title, title);
value!(TitleRegex, title_regex); value!(TitleRegex, title_regex);
value!(AppId, app_id);
value!(AppIdRegex, app_id_regex);
Some(()) Some(())
} }

View file

@ -1787,6 +1787,14 @@
"title-regex": { "title-regex": {
"type": "string", "type": "string",
"description": "Matches the title of the window with a regular expression." "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": [] "required": []

View file

@ -4022,6 +4022,18 @@ The table has the following fields:
The value of this field should be a string. 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.
<a name="types-WindowMatchExactly"></a> <a name="types-WindowMatchExactly"></a>
### `WindowMatchExactly` ### `WindowMatchExactly`

View file

@ -3475,6 +3475,14 @@ WindowMatch:
kind: string kind: string
required: false required: false
description: Matches the title of the window with a regular expression. 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: WindowMatchExactly: