use { crate::{ State, config::{ClientMatch, ClientRule, GenericMatch, WindowMatch, WindowRule}, }, ahash::{AHashMap, AHashSet}, jay_config::{ client::{ClientCriterion, ClientMatcher}, window::{WindowCriterion, WindowMatcher}, }, std::{mem::ManuallyDrop, rc::Rc}, }; impl State { pub fn create_rules(self: &Rc, rules: &[R]) -> (Vec>, RuleMapper) where R: Rule, { let mut names = AHashMap::new(); for (idx, rule) in rules.iter().enumerate() { if let Some(name) = rule.name() { names.insert(name.to_string(), idx); } } let mut mapper = RuleMapper { state: self.clone(), names, pending: Default::default(), mapped: Default::default(), }; let mut matchers = vec![]; for idx in 0..rules.len() { if let Some(matcher) = mapper.map_rule(rules, idx) { matchers.push(MatcherTemp(matcher)); } } (matchers, mapper) } } pub trait Rule: Sized + 'static { type Match; type Matcher: Copy + 'static; type Criterion<'a>; const NAME_UPPER: &str; const NAME_LOWER: &str; fn name(&self) -> Option<&str>; fn match_(&self) -> &Self::Match; fn generic(m: &Self::Match) -> &GenericMatch; fn map_custom( state: &Rc, all: &mut Vec>, match_: &Self::Match, ) -> Option<()>; fn create(c: Self::Criterion<'_>) -> Self::Matcher; fn destroy(m: Self::Matcher); fn bind(&self, state: &Rc, matcher: Self::Matcher); fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static>; fn gen_not<'a, 'b: 'a>(m: &'a Self::Criterion<'b>) -> Self::Criterion<'a>; fn gen_all<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a>; fn gen_any<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a>; fn gen_exactly<'a, 'b: 'a>(n: usize, m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a>; } impl Rule for ClientRule { type Match = ClientMatch; type Matcher = ClientMatcher; type Criterion<'a> = ClientCriterion<'a>; const NAME_UPPER: &str = "Client"; const NAME_LOWER: &str = "client"; fn name(&self) -> Option<&str> { self.name.as_deref() } fn match_(&self) -> &Self::Match { &self.match_ } fn generic(m: &Self::Match) -> &GenericMatch { &m.generic } fn map_custom( _state: &Rc, all: &mut Vec>, match_: &Self::Match, ) -> Option<()> { let m = |c: ClientCriterion<'_>| MatcherTemp(c.to_matcher()); macro_rules! value_ref { ($ty:ident, $field:ident) => { if let Some(value) = &match_.$field { all.push(m(ClientCriterion::$ty(value))); } }; } macro_rules! value { ($ty:ident, $field:ident) => { if let Some(value) = match_.$field { all.push(m(ClientCriterion::$ty(value))); } }; } macro_rules! bool { ($ty:ident, $field:ident) => { if let Some(value) = &match_.$field { let crit = ClientCriterion::$ty; let matcher = match value { false => m(ClientCriterion::Not(&crit)), true => m(crit), }; all.push(matcher); } }; } value_ref!(SandboxEngine, sandbox_engine); value_ref!(SandboxEngineRegex, sandbox_engine_regex); value_ref!(SandboxAppId, sandbox_app_id); value_ref!(SandboxAppIdRegex, sandbox_app_id_regex); value_ref!(SandboxInstanceId, sandbox_instance_id); value_ref!(SandboxInstanceIdRegex, sandbox_instance_id_regex); value_ref!(Comm, comm); value_ref!(CommRegex, comm_regex); value_ref!(Exe, exe); value_ref!(ExeRegex, exe_regex); value_ref!(Tag, tag); value_ref!(TagRegex, tag_regex); value!(Uid, uid); value!(Pid, pid); bool!(Sandboxed, sandboxed); bool!(IsXwayland, is_xwayland); Some(()) } fn create(c: Self::Criterion<'_>) -> Self::Matcher { c.to_matcher() } fn destroy(m: Self::Matcher) { m.destroy(); } fn bind(&self, state: &Rc, matcher: Self::Matcher) { let state = state.clone(); macro_rules! latch { ($g:ident, $client:ident) => { let g = $g.clone(); let state = state.clone(); $client.latch(move || { state.with_client($client.client(), true, || g()); }); }; } if let Some(action) = &self.action { let f = action.clone().into_fn(&state); if let Some(action) = &self.latch { let g = action.clone().into_rc_fn(&state); let state = state.clone(); matcher.bind(move |client| { state.with_client(client.client(), false, &f); latch!(g, client); }); } else { matcher.bind(move |client| { state.with_client(client.client(), false, &f); }); } } else { if let Some(action) = &self.latch { let g = action.clone().into_rc_fn(&state); matcher.bind(move |client| { latch!(g, client); }); } } if let Some(caps) = self.capabilities { matcher.set_capabilities(caps); } if let Some(caps) = self.bounding_capabilities { matcher.set_sandbox_bounding_capabilities(caps); } } fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static> { ClientCriterion::Matcher(m) } fn gen_not<'a, 'b: 'a>(m: &'a Self::Criterion<'b>) -> Self::Criterion<'a> { ClientCriterion::Not(m) } fn gen_all<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> { ClientCriterion::All(m) } fn gen_any<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> { ClientCriterion::Any(m) } fn gen_exactly<'a, 'b: 'a>(n: usize, m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> { ClientCriterion::Exactly(n, m) } } impl Rule for WindowRule { type Match = WindowMatch; type Matcher = WindowMatcher; type Criterion<'a> = WindowCriterion<'a>; const NAME_UPPER: &str = "Window"; const NAME_LOWER: &str = "window"; fn name(&self) -> Option<&str> { self.name.as_deref() } fn match_(&self) -> &Self::Match { &self.match_ } fn generic(m: &Self::Match) -> &GenericMatch { &m.generic } fn map_custom( state: &Rc, all: &mut Vec>, match_: &Self::Match, ) -> Option<()> { let m = |c: WindowCriterion<'_>| MatcherTemp(c.to_matcher()); macro_rules! value { ($ty:ident, $field:ident) => { if let Some(value) = &match_.$field { all.push(m(WindowCriterion::$ty(value))); } }; } macro_rules! bool { ($ty:ident, $field:ident) => { if let Some(value) = &match_.$field { let crit = WindowCriterion::$ty; let matcher = match value { false => m(WindowCriterion::Not(&crit)), true => m(crit), }; all.push(matcher); } }; } if let Some(value) = &match_.types { all.push(m(WindowCriterion::Types(*value))); } if let Some(value) = &match_.client { let mut mapper = state.persistent.client_rule_mapper.borrow_mut(); let mapper = mapper.as_mut()?; let matcher = mapper.map_temporary_match(&[], value)?; all.push(m(WindowCriterion::Client(&ClientCriterion::Matcher( matcher.0, )))); } value!(Title, title); value!(TitleRegex, title_regex); value!(AppId, app_id); value!(AppIdRegex, app_id_regex); value!(Tag, tag); value!(TagRegex, tag_regex); value!(XClass, x_class); value!(XClassRegex, x_class_regex); value!(XInstance, x_instance); value!(XInstanceRegex, x_instance_regex); value!(XRole, x_role); value!(XRoleRegex, x_role_regex); value!(WorkspaceName, workspace); value!(WorkspaceNameRegex, workspace_regex); bool!(Floating, floating); bool!(Visible, visible); bool!(Urgent, urgent); bool!(Fullscreen, fullscreen); bool!(JustMapped, just_mapped); if let Some(value) = match_.focused { let crit = WindowCriterion::Focus(state.persistent.seat); let matcher = match value { false => m(WindowCriterion::Not(&crit)), true => m(crit), }; all.push(matcher); } if let Some(value) = &match_.content_types { all.push(m(WindowCriterion::ContentTypes(*value))); } Some(()) } fn create(c: Self::Criterion<'_>) -> Self::Matcher { c.to_matcher() } fn destroy(m: Self::Matcher) { m.destroy(); } fn bind(&self, state: &Rc, matcher: Self::Matcher) { let state = state.clone(); macro_rules! latch { ($g:ident, $client:ident, $win:ident) => { let g = $g.clone(); let state = state.clone(); $win.latch(move || { state.with_client($client, true, || { state.with_window(*$win, true, || g()); }); }); }; } if let Some(action) = &self.action { let f = action.clone().into_fn(&state); if let Some(action) = &self.latch { let g = action.clone().into_rc_fn(&state); matcher.bind(move |win| { let client = win.client(); state.with_client(client, false, || { state.with_window(*win, false, &f); }); latch!(g, client, win); }); } else { matcher.bind(move |win| { let client = win.client(); state.with_client(client, false, || { state.with_window(*win, false, &f); }); }); } } else { if let Some(action) = &self.latch { let g = action.clone().into_rc_fn(&state); matcher.bind(move |win| { let client = win.client(); latch!(g, client, win); }); } } if let Some(auto_focus) = self.auto_focus { matcher.set_auto_focus(auto_focus); } if let Some(tile_state) = self.initial_tile_state { matcher.set_initial_tile_state(tile_state); } } fn gen_matcher(m: Self::Matcher) -> Self::Criterion<'static> { WindowCriterion::Matcher(m) } fn gen_not<'a, 'b: 'a>(m: &'a Self::Criterion<'b>) -> Self::Criterion<'a> { WindowCriterion::Not(m) } fn gen_all<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> { WindowCriterion::All(m) } fn gen_any<'a, 'b: 'a>(m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> { WindowCriterion::Any(m) } fn gen_exactly<'a, 'b: 'a>(n: usize, m: &'a [Self::Criterion<'b>]) -> Self::Criterion<'a> { WindowCriterion::Exactly(n, m) } } pub struct RuleMapper where R: Rule, { state: Rc, names: AHashMap, pending: AHashSet, mapped: AHashMap, } pub struct MatcherTemp(R::Matcher) where R: Rule; impl Drop for MatcherTemp where R: Rule, { fn drop(&mut self) { R::destroy(self.0); } } impl RuleMapper where R: Rule, { fn map_rule(&mut self, rules: &[R], idx: usize) -> Option { if let Some(matcher) = self.mapped.get(&idx) { return Some(*matcher); } if !self.pending.insert(idx) { if let Some(name) = rules.get(idx).and_then(|r| r.name()) { log::error!("{} rule `{name}` has a loop", R::NAME_UPPER); } return None; } let rule = &rules[idx]; let matcher = self.map_match(rules, rule.match_())?; self.mapped.insert(idx, matcher); rule.bind(&self.state, matcher); Some(matcher) } fn map_temporary_match(&mut self, rules: &[R], matcher: &R::Match) -> Option> { self.map_match(rules, matcher).map(MatcherTemp) } fn map_match(&mut self, rules: &[R], matcher: &R::Match) -> Option { let mut all = vec![]; self.map_generic_match(rules, &mut all, R::generic(matcher))?; R::map_custom(&self.state, &mut all, matcher)?; if all.len() == 1 { return Some(ManuallyDrop::new(all.pop().unwrap()).0); } let all: Vec<_> = all.iter().map(|m| R::gen_matcher(m.0)).collect(); Some(R::create(R::gen_all(&all))) } fn map_generic_match( &mut self, rules: &[R], all: &mut Vec>, matcher: &GenericMatch, ) -> Option<()> { let m = |c: R::Criterion<'_>| MatcherTemp(R::create(c)); if let Some(name) = &matcher.name { let Some(&idx) = self.names.get(&**name) else { log::error!("There is no {} rule named `{name}`", R::NAME_LOWER); return None; }; let matcher = self.map_rule(rules, idx)?; all.push(m(R::gen_matcher(matcher))); } if let Some(not) = &matcher.not { let matcher = self.map_temporary_match(rules, not)?; all.push(m(R::gen_not(&R::gen_matcher(matcher.0)))); } if let Some(list) = &matcher.all { for match_ in list { all.push(self.map_temporary_match(rules, match_)?); } } if let Some(list) = &matcher.any { let mut any = vec![]; for match_ in list { any.push(self.map_temporary_match(rules, match_)?); } let any: Vec<_> = any.iter().map(|m| R::gen_matcher(m.0)).collect(); all.push(m(R::gen_any(&any))); } if let Some(exactly) = &matcher.exactly { let mut list = vec![]; for match_ in &exactly.list { list.push(self.map_temporary_match(rules, match_)?); } let list: Vec<_> = list.iter().map(|m| R::gen_matcher(m.0)).collect(); all.push(m(R::gen_exactly(exactly.num, &list))) } Some(()) } }