config: add title window criteria
This commit is contained in:
parent
2b5be7fbd9
commit
6ef7655dbd
14 changed files with 109 additions and 23 deletions
|
|
@ -113,4 +113,6 @@ 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,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
_private::{
|
_private::{
|
||||||
ClientCriterionIpc, ClientCriterionStringField, Config, ConfigEntry, ConfigEntryGen,
|
ClientCriterionIpc, ClientCriterionStringField, Config, ConfigEntry, ConfigEntryGen,
|
||||||
GenericCriterionIpc, PollableId, VERSION, WindowCriterionIpc, WireMode, bincode_ops,
|
GenericCriterionIpc, PollableId, VERSION, WindowCriterionIpc,
|
||||||
|
WindowCriterionStringField, WireMode, bincode_ops,
|
||||||
ipc::{
|
ipc::{
|
||||||
ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource,
|
ClientMessage, InitMessage, Response, ServerFeature, ServerMessage, WorkspaceSource,
|
||||||
},
|
},
|
||||||
|
|
@ -1609,7 +1610,6 @@ impl ConfigClient {
|
||||||
criterion: WindowCriterion,
|
criterion: WindowCriterion,
|
||||||
child: bool,
|
child: bool,
|
||||||
) -> (WindowMatcher, bool) {
|
) -> (WindowMatcher, bool) {
|
||||||
#[expect(unused_macros)]
|
|
||||||
macro_rules! string {
|
macro_rules! string {
|
||||||
($t:expr, $field:ident, $regex:expr) => {
|
($t:expr, $field:ident, $regex:expr) => {
|
||||||
WindowCriterionIpc::String {
|
WindowCriterionIpc::String {
|
||||||
|
|
@ -1653,6 +1653,8 @@ impl ConfigClient {
|
||||||
}
|
}
|
||||||
WindowCriterionIpc::Client(matcher)
|
WindowCriterionIpc::Client(matcher)
|
||||||
}
|
}
|
||||||
|
WindowCriterion::Title(t) => string!(t, Title, false),
|
||||||
|
WindowCriterion::TitleRegex(t) => string!(t, Title, true),
|
||||||
};
|
};
|
||||||
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
|
let res = self.send_with_response(&ClientMessage::CreateWindowMatcher { criterion });
|
||||||
get_response!(
|
get_response!(
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,10 @@ pub enum WindowCriterion<'a> {
|
||||||
Exactly(usize, &'a [WindowCriterion<'a>]),
|
Exactly(usize, &'a [WindowCriterion<'a>]),
|
||||||
/// Matches if the window's client matches the client criterion.
|
/// Matches if the window's client matches the client criterion.
|
||||||
Client(&'a ClientCriterion<'a>),
|
Client(&'a ClientCriterion<'a>),
|
||||||
|
/// Matches the title of the window verbatim.
|
||||||
|
Title(&'a str),
|
||||||
|
/// Matches the title of the window with a regular expression.
|
||||||
|
TitleRegex(&'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowCriterion<'_> {
|
impl WindowCriterion<'_> {
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ use {
|
||||||
jay_config::{
|
jay_config::{
|
||||||
_private::{
|
_private::{
|
||||||
ClientCriterionIpc, ClientCriterionStringField, GenericCriterionIpc, PollableId,
|
ClientCriterionIpc, ClientCriterionStringField, GenericCriterionIpc, PollableId,
|
||||||
WindowCriterionIpc, WireMode, bincode_ops,
|
WindowCriterionIpc, WindowCriterionStringField, WireMode, bincode_ops,
|
||||||
ipc::{ClientMessage, Response, ServerMessage, WorkspaceSource},
|
ipc::{ClientMessage, Response, ServerMessage, WorkspaceSource},
|
||||||
},
|
},
|
||||||
Axis, Direction, Workspace,
|
Axis, Direction, Workspace,
|
||||||
|
|
@ -1980,7 +1980,6 @@ impl ConfigProxyHandler {
|
||||||
field,
|
field,
|
||||||
regex,
|
regex,
|
||||||
} => {
|
} => {
|
||||||
#[expect(unused_variables)]
|
|
||||||
let needle = match *regex {
|
let needle = match *regex {
|
||||||
true => {
|
true => {
|
||||||
let regex = Regex::new(string).map_err(CphError::InvalidRegex)?;
|
let regex = Regex::new(string).map_err(CphError::InvalidRegex)?;
|
||||||
|
|
@ -1988,7 +1987,9 @@ impl ConfigProxyHandler {
|
||||||
}
|
}
|
||||||
false => CritLiteralOrRegex::Literal(string.to_string()),
|
false => CritLiteralOrRegex::Literal(string.to_string()),
|
||||||
};
|
};
|
||||||
match *field {}
|
match *field {
|
||||||
|
WindowCriterionStringField::Title => mgr.title(needle),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
WindowCriterionIpc::Types(t) => mgr.kind(*t),
|
WindowCriterionIpc::Types(t) => mgr.kind(*t),
|
||||||
WindowCriterionIpc::Client(c) => {
|
WindowCriterionIpc::Client(c) => {
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ pub mod tlm_matchers;
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
criteria::{
|
criteria::{
|
||||||
CritDestroyListener, CritMatcherId, CritMatcherIds, CritMgrExt, CritUpstreamNode,
|
CritDestroyListener, CritLiteralOrRegex, CritMatcherId, CritMatcherIds, CritMgrExt,
|
||||||
FixedRootMatcher, RootMatcherMap,
|
CritUpstreamNode, FixedRootMatcher, RootMatcherMap,
|
||||||
clm::ClmUpstreamNode,
|
clm::ClmUpstreamNode,
|
||||||
crit_graph::{CritMgr, CritTarget, CritTargetOwner, WeakCritTargetOwner},
|
crit_graph::{CritMgr, CritTarget, CritTargetOwner, WeakCritTargetOwner},
|
||||||
crit_leaf::{CritLeafEvent, CritLeafMatcher},
|
crit_leaf::{CritLeafEvent, CritLeafMatcher},
|
||||||
crit_matchers::critm_constant::CritMatchConstant,
|
crit_matchers::critm_constant::CritMatchConstant,
|
||||||
tlm::tlm_matchers::{tlmm_client::TlmMatchClient, tlmm_kind::TlmMatchKind},
|
tlm::tlm_matchers::{
|
||||||
|
tlmm_client::TlmMatchClient, tlmm_kind::TlmMatchKind, tlmm_string::TlmMatchTitle,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
state::State,
|
state::State,
|
||||||
tree::{NodeId, ToplevelData, ToplevelNode},
|
tree::{NodeId, ToplevelData, ToplevelNode},
|
||||||
|
|
@ -26,6 +28,7 @@ bitflags! {
|
||||||
TlMatcherChange: u32;
|
TlMatcherChange: u32;
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
|
type TlmFixedRootMatcher<T> = FixedRootMatcher<ToplevelData, T>;
|
||||||
|
|
@ -44,6 +47,7 @@ type TlmRootMatcherMap<T> = RootMatcherMap<ToplevelData, T>;
|
||||||
pub struct RootMatchers {
|
pub struct RootMatchers {
|
||||||
kinds: TlmRootMatcherMap<TlmMatchKind>,
|
kinds: TlmRootMatcherMap<TlmMatchKind>,
|
||||||
clients: CopyHashMap<CritMatcherId, Weak<TlmMatchClient>>,
|
clients: CopyHashMap<CritMatcherId, Weak<TlmMatchClient>>,
|
||||||
|
title: TlmRootMatcherMap<TlmMatchTitle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_tl_changes(state: Rc<State>) {
|
pub async fn handle_tl_changes(state: Rc<State>) {
|
||||||
|
|
@ -123,7 +127,6 @@ impl TlMatcherManager {
|
||||||
}
|
}
|
||||||
change |= TlMatcherChange::all();
|
change |= TlMatcherChange::all();
|
||||||
}
|
}
|
||||||
#[expect(unused_macros)]
|
|
||||||
macro_rules! conditional {
|
macro_rules! conditional {
|
||||||
($change:expr, $field:ident) => {
|
($change:expr, $field:ident) => {
|
||||||
if change.contains($change) && self.matchers.$field.is_not_empty() {
|
if change.contains($change) && self.matchers.$field.is_not_empty() {
|
||||||
|
|
@ -139,6 +142,7 @@ impl TlMatcherManager {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
conditional!(TL_CHANGED_TITLE, title);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,7 +192,6 @@ impl TlMatcherManager {
|
||||||
unconditional!(clients);
|
unconditional!(clients);
|
||||||
self.constant[true].handle(data);
|
self.constant[true].handle(data);
|
||||||
}
|
}
|
||||||
#[expect(unused_macros)]
|
|
||||||
macro_rules! conditional {
|
macro_rules! conditional {
|
||||||
($change:expr, $field:ident) => {
|
($change:expr, $field:ident) => {
|
||||||
if changed.contains($change) {
|
if changed.contains($change) {
|
||||||
|
|
@ -206,6 +209,11 @@ impl TlMatcherManager {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
conditional!(TL_CHANGED_TITLE, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn title(&self, string: CritLiteralOrRegex) -> Rc<TlmUpstreamNode> {
|
||||||
|
self.root(TlmMatchTitle::new(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self, kind: WindowType) -> Rc<TlmUpstreamNode> {
|
pub fn kind(&self, kind: WindowType) -> Rc<TlmUpstreamNode> {
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,4 @@ macro_rules! fixed_root_criterion {
|
||||||
|
|
||||||
pub mod tlmm_client;
|
pub mod tlmm_client;
|
||||||
pub mod tlmm_kind;
|
pub mod tlmm_kind;
|
||||||
|
pub mod tlmm_string;
|
||||||
|
|
|
||||||
23
src/criteria/tlm/tlm_matchers/tlmm_string.rs
Normal file
23
src/criteria/tlm/tlm_matchers/tlmm_string.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::{
|
||||||
|
criteria::{
|
||||||
|
crit_matchers::critm_string::{CritMatchString, StringAccess},
|
||||||
|
tlm::{RootMatchers, TlmRootMatcherMap},
|
||||||
|
},
|
||||||
|
tree::ToplevelData,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type TlmMatchString<T> = CritMatchString<ToplevelData, T>;
|
||||||
|
|
||||||
|
pub type TlmMatchTitle = TlmMatchString<TitleAccess>;
|
||||||
|
|
||||||
|
pub struct TitleAccess;
|
||||||
|
|
||||||
|
impl StringAccess<ToplevelData> for TitleAccess {
|
||||||
|
fn with_string(data: &ToplevelData, f: impl FnOnce(&str) -> bool) -> bool {
|
||||||
|
f(&data.title.borrow())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nodes(roots: &RootMatchers) -> &TlmRootMatcherMap<TlmMatchString<Self>> {
|
||||||
|
&roots.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ use {
|
||||||
client::{Client, ClientId},
|
client::{Client, ClientId},
|
||||||
criteria::{
|
criteria::{
|
||||||
CritDestroyListener, CritMatcherId,
|
CritDestroyListener, CritMatcherId,
|
||||||
tlm::{TL_CHANGED_DESTROYED, TL_CHANGED_NEW, TlMatcherChange},
|
tlm::{TL_CHANGED_DESTROYED, TL_CHANGED_NEW, TL_CHANGED_TITLE, TlMatcherChange},
|
||||||
},
|
},
|
||||||
ifs::{
|
ifs::{
|
||||||
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
|
ext_foreign_toplevel_handle_v1::ExtForeignToplevelHandleV1,
|
||||||
|
|
@ -92,6 +92,7 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
|
||||||
.clone_from(&title);
|
.clone_from(&title);
|
||||||
data.placeholder.tl_title_changed();
|
data.placeholder.tl_title_changed();
|
||||||
}
|
}
|
||||||
|
data.property_changed(TL_CHANGED_TITLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tl_set_parent(&self, parent: Rc<dyn ContainingNode>) {
|
fn tl_set_parent(&self, parent: Rc<dyn ContainingNode>) {
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,8 @@ pub struct WindowMatch {
|
||||||
pub generic: GenericMatch<Self>,
|
pub generic: GenericMatch<Self>,
|
||||||
pub types: Option<WindowType>,
|
pub types: Option<WindowType>,
|
||||||
pub client: Option<ClientMatch>,
|
pub client: Option<ClientMatch>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub title_regex: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -44,16 +44,27 @@ 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 ((name, not_val, all_val, any_val, exactly_val, types_val, client_val),) = ext
|
let ((
|
||||||
.extract(((
|
name,
|
||||||
opt(str("name")),
|
not_val,
|
||||||
opt(val("not")),
|
all_val,
|
||||||
opt(arr("all")),
|
any_val,
|
||||||
opt(arr("any")),
|
exactly_val,
|
||||||
opt(val("exactly")),
|
types_val,
|
||||||
opt(val("types")),
|
client_val,
|
||||||
opt(val("client")),
|
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 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))?));
|
||||||
|
|
@ -93,6 +104,8 @@ impl Parser for WindowMatchParser<'_> {
|
||||||
any,
|
any,
|
||||||
exactly,
|
exactly,
|
||||||
},
|
},
|
||||||
|
title: title.despan_into(),
|
||||||
|
title_regex: title_regex.despan_into(),
|
||||||
types,
|
types,
|
||||||
client,
|
client,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,6 @@ impl Rule for WindowRule {
|
||||||
match_: &Self::Match,
|
match_: &Self::Match,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let m = |c: WindowCriterion<'_>| MatcherTemp(c.to_matcher());
|
let m = |c: WindowCriterion<'_>| MatcherTemp(c.to_matcher());
|
||||||
#[expect(unused_macros)]
|
|
||||||
macro_rules! value {
|
macro_rules! value {
|
||||||
($ty:ident, $field:ident) => {
|
($ty:ident, $field:ident) => {
|
||||||
if let Some(value) = &match_.$field {
|
if let Some(value) = &match_.$field {
|
||||||
|
|
@ -256,6 +255,8 @@ impl Rule for WindowRule {
|
||||||
matcher.0,
|
matcher.0,
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
|
value!(Title, title);
|
||||||
|
value!(TitleRegex, title_regex);
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1779,6 +1779,14 @@
|
||||||
"client": {
|
"client": {
|
||||||
"description": "Matches if the window's client matches the client criterion.",
|
"description": "Matches if the window's client matches the client criterion.",
|
||||||
"$ref": "#/$defs/ClientMatch"
|
"$ref": "#/$defs/ClientMatch"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Matches the title of the window verbatim."
|
||||||
|
},
|
||||||
|
"title-regex": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Matches the title of the window with a regular expression."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
|
|
||||||
|
|
@ -4010,6 +4010,18 @@ The table has the following fields:
|
||||||
|
|
||||||
The value of this field should be a [ClientMatch](#types-ClientMatch).
|
The value of this field should be a [ClientMatch](#types-ClientMatch).
|
||||||
|
|
||||||
|
- `title` (optional):
|
||||||
|
|
||||||
|
Matches the title of the window verbatim.
|
||||||
|
|
||||||
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
- `title-regex` (optional):
|
||||||
|
|
||||||
|
Matches the title 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`
|
||||||
|
|
|
||||||
|
|
@ -3467,6 +3467,14 @@ WindowMatch:
|
||||||
ref: ClientMatch
|
ref: ClientMatch
|
||||||
required: false
|
required: false
|
||||||
description: Matches if the window's client matches the client criterion.
|
description: Matches if the window's client matches the client criterion.
|
||||||
|
title:
|
||||||
|
kind: string
|
||||||
|
required: false
|
||||||
|
description: Matches the title of the window verbatim.
|
||||||
|
title-regex:
|
||||||
|
kind: string
|
||||||
|
required: false
|
||||||
|
description: Matches the title of the window with a regular expression.
|
||||||
|
|
||||||
|
|
||||||
WindowMatchExactly:
|
WindowMatchExactly:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue