From d6e0ecc507d5307d4ed416ddf62ab76ef2aff2f9 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Wed, 20 Mar 2024 12:24:12 -0700 Subject: [PATCH] feat(config): add support for composite rules This commit adds support for composite rules in the static configuration file, and for float_identifiers in the applications.yaml file. A new enum, MatchingRule provides two variants, Simple and Composite, and is now used in place of IdWithIdentifier throughout komorebi. In order to keep this new enum lean, a variant for IdWithIdentifierAndComment has not been added, and references to it in the old config generation code have been replaced with MatchingRule. resolve #462, resolve #715, resolve #237 --- komorebi-core/src/config_generation.rs | 61 +++-- komorebi/src/lib.rs | 58 ++--- komorebi/src/process_command.rs | 61 +++-- komorebi/src/static_config.rs | 324 ++++++++++-------------- komorebi/src/window.rs | 331 ++++++++++++++----------- komorebi/src/window_manager.rs | 14 +- 6 files changed, 426 insertions(+), 423 deletions(-) diff --git a/komorebi-core/src/config_generation.rs b/komorebi-core/src/config_generation.rs index 0525c44d..b02a683a 100644 --- a/komorebi-core/src/config_generation.rs +++ b/komorebi-core/src/config_generation.rs @@ -8,7 +8,9 @@ use strum::EnumString; use crate::ApplicationIdentifier; -#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema)] +#[derive( + Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema, +)] #[strum(serialize_all = "snake_case")] #[serde(rename_all = "snake_case")] pub enum ApplicationOptions { @@ -50,6 +52,13 @@ impl ApplicationOptions { } } +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum MatchingRule { + Simple(IdWithIdentifier), + Composite(Vec), +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct IdWithIdentifier { pub kind: ApplicationIdentifier, @@ -95,7 +104,7 @@ pub struct ApplicationConfiguration { #[serde(skip_serializing_if = "Option::is_none")] pub options: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub float_identifiers: Option>, + pub float_identifiers: Option>, } impl ApplicationConfiguration { @@ -181,19 +190,21 @@ impl ApplicationConfigurationGenerator { } if let Some(float_identifiers) = app.float_identifiers { - for float in float_identifiers { - let float_rule = - format!("komorebic.exe float-rule {} \"{}\"", float.kind, float.id); + for matching_rule in float_identifiers { + if let MatchingRule::Simple(float) = matching_rule { + let float_rule = + format!("komorebic.exe float-rule {} \"{}\"", float.kind, float.id); - // Don't want to send duped signals especially as configs get larger - if !float_rules.contains(&float_rule) { - float_rules.push(float_rule.clone()); + // Don't want to send duped signals especially as configs get larger + if !float_rules.contains(&float_rule) { + float_rules.push(float_rule.clone()); - if let Some(comment) = float.comment { - lines.push(format!("# {comment}")); - }; + // if let Some(comment) = float.comment { + // lines.push(format!("# {comment}")); + // }; - lines.push(float_rule); + lines.push(float_rule); + } } } } @@ -230,21 +241,23 @@ impl ApplicationConfigurationGenerator { } if let Some(float_identifiers) = app.float_identifiers { - for float in float_identifiers { - let float_rule = format!( - "RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")", - float.kind, float.id - ); + for matching_rule in float_identifiers { + if let MatchingRule::Simple(float) = matching_rule { + let float_rule = format!( + "RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")", + float.kind, float.id + ); - // Don't want to send duped signals especially as configs get larger - if !float_rules.contains(&float_rule) { - float_rules.push(float_rule.clone()); + // Don't want to send duped signals especially as configs get larger + if !float_rules.contains(&float_rule) { + float_rules.push(float_rule.clone()); - if let Some(comment) = float.comment { - lines.push(format!("; {comment}")); - }; + // if let Some(comment) = float.comment { + // lines.push(format!("; {comment}")); + // }; - lines.push(float_rule); + lines.push(float_rule); + } } } } diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index 1514fc80..ab366fe5 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -46,6 +46,7 @@ pub use windows_api::*; use color_eyre::Result; use komorebi_core::config_generation::IdWithIdentifier; +use komorebi_core::config_generation::MatchingRule; use komorebi_core::config_generation::MatchingStrategy; use komorebi_core::ApplicationIdentifier; use komorebi_core::HidingBehaviour; @@ -66,57 +67,57 @@ type WorkspaceRule = (usize, usize, bool); lazy_static! { static ref HIDDEN_HWNDS: Arc>> = Arc::new(Mutex::new(vec![])); - static ref LAYERED_WHITELIST: Arc>> = Arc::new(Mutex::new(vec![ - IdWithIdentifier { + static ref LAYERED_WHITELIST: Arc>> = Arc::new(Mutex::new(vec![ + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Exe, id: String::from("steam.exe"), matching_strategy: Option::from(MatchingStrategy::Equals), - }, + }), ])); - static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc>> = + static ref TRAY_AND_MULTI_WINDOW_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![ - IdWithIdentifier { + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Exe, id: String::from("explorer.exe"), matching_strategy: Option::from(MatchingStrategy::Equals), - }, - IdWithIdentifier { + }), + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Exe, id: String::from("firefox.exe"), matching_strategy: Option::from(MatchingStrategy::Equals), - }, - IdWithIdentifier { + }), + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Exe, id: String::from("chrome.exe"), matching_strategy: Option::from(MatchingStrategy::Equals), - }, - IdWithIdentifier { + }), + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Exe, id: String::from("idea64.exe"), matching_strategy: Option::from(MatchingStrategy::Equals), - }, - IdWithIdentifier { + }), + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Exe, id: String::from("ApplicationFrameHost.exe"), matching_strategy: Option::from(MatchingStrategy::Equals), - }, - IdWithIdentifier { + }), + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Exe, id: String::from("steam.exe"), matching_strategy: Option::from(MatchingStrategy::Equals), - } + }) ])); - static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc>> = Arc::new(Mutex::new(vec![ - IdWithIdentifier { + static ref OBJECT_NAME_CHANGE_ON_LAUNCH: Arc>> = Arc::new(Mutex::new(vec![ + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Exe, id: String::from("firefox.exe"), matching_strategy: Option::from(MatchingStrategy::Equals), - }, - IdWithIdentifier { + }), + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Exe, id: String::from("idea64.exe"), matching_strategy: Option::from(MatchingStrategy::Equals), - }, + }), ])); static ref MONITOR_INDEX_PREFERENCES: Arc>> = Arc::new(Mutex::new(HashMap::new())); @@ -126,25 +127,26 @@ lazy_static! { Arc::new(Mutex::new(HashMap::new())); static ref REGEX_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(HashMap::new())); - static ref MANAGE_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![])); - static ref FLOAT_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![ + static ref MANAGE_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![])); + static ref FLOAT_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![ // mstsc.exe creates these on Windows 11 when a WSL process is launched // https://github.com/LGUG2Z/komorebi/issues/74 - IdWithIdentifier { + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Class, id: String::from("OPContainerClass"), matching_strategy: Option::from(MatchingStrategy::Equals), - }, - IdWithIdentifier { + }), + MatchingRule::Simple(IdWithIdentifier { kind: ApplicationIdentifier::Class, id: String::from("IHWindowClass"), matching_strategy: Option::from(MatchingStrategy::Equals), - } + }) ])); + static ref PERMAIGNORE_CLASSES: Arc>> = Arc::new(Mutex::new(vec![ "Chrome_RenderWidgetHostHWND".to_string(), ])); - static ref BORDER_OVERFLOW_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![])); + static ref BORDER_OVERFLOW_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![])); static ref WSL2_UI_PROCESSES: Arc>> = Arc::new(Mutex::new(vec![ "X410.exe".to_string(), "vcxsrv.exe".to_string(), diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 12081758..2794708c 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -23,6 +23,7 @@ use uds_windows::UnixStream; use komorebi_core::config_generation::ApplicationConfiguration; use komorebi_core::config_generation::IdWithIdentifier; +use komorebi_core::config_generation::MatchingRule; use komorebi_core::config_generation::MatchingStrategy; use komorebi_core::ApplicationIdentifier; use komorebi_core::Axis; @@ -273,17 +274,19 @@ impl WindowManager { let mut should_push = true; for m in &*manage_identifiers { - if m.id.eq(id) { - should_push = false; + if let MatchingRule::Simple(m) = m { + if m.id.eq(id) { + should_push = false; + } } } if should_push { - manage_identifiers.push(IdWithIdentifier { + manage_identifiers.push(MatchingRule::Simple(IdWithIdentifier { kind: identifier, id: id.clone(), matching_strategy: Option::from(MatchingStrategy::Legacy), - }); + })); } } SocketMessage::FloatRule(identifier, ref id) => { @@ -291,17 +294,19 @@ impl WindowManager { let mut should_push = true; for f in &*float_identifiers { - if f.id.eq(id) { - should_push = false; + if let MatchingRule::Simple(f) = f { + if f.id.eq(id) { + should_push = false; + } } } if should_push { - float_identifiers.push(IdWithIdentifier { + float_identifiers.push(MatchingRule::Simple(IdWithIdentifier { kind: identifier, id: id.clone(), matching_strategy: Option::from(MatchingStrategy::Legacy), - }); + })); } let offset = self.work_area_offset; @@ -1025,17 +1030,19 @@ impl WindowManager { let mut should_push = true; for i in &*identifiers { - if i.id.eq(id) { - should_push = false; + if let MatchingRule::Simple(i) = i { + if i.id.eq(id) { + should_push = false; + } } } if should_push { - identifiers.push(IdWithIdentifier { + identifiers.push(MatchingRule::Simple(IdWithIdentifier { kind: identifier, id: id.clone(), matching_strategy: Option::from(MatchingStrategy::Legacy), - }); + })); } } SocketMessage::IdentifyObjectNameChangeApplication(identifier, ref id) => { @@ -1043,34 +1050,38 @@ impl WindowManager { let mut should_push = true; for i in &*identifiers { - if i.id.eq(id) { - should_push = false; + if let MatchingRule::Simple(i) = i { + if i.id.eq(id) { + should_push = false; + } } } if should_push { - identifiers.push(IdWithIdentifier { + identifiers.push(MatchingRule::Simple(IdWithIdentifier { kind: identifier, id: id.clone(), matching_strategy: Option::from(MatchingStrategy::Legacy), - }); + })); } } SocketMessage::IdentifyTrayApplication(identifier, ref id) => { let mut identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock(); let mut should_push = true; for i in &*identifiers { - if i.id.eq(id) { - should_push = false; + if let MatchingRule::Simple(i) = i { + if i.id.eq(id) { + should_push = false; + } } } if should_push { - identifiers.push(IdWithIdentifier { + identifiers.push(MatchingRule::Simple(IdWithIdentifier { kind: identifier, id: id.clone(), matching_strategy: Option::from(MatchingStrategy::Legacy), - }); + })); } } SocketMessage::IdentifyLayeredApplication(identifier, ref id) => { @@ -1078,17 +1089,19 @@ impl WindowManager { let mut should_push = true; for i in &*identifiers { - if i.id.eq(id) { - should_push = false; + if let MatchingRule::Simple(i) = i { + if i.id.eq(id) { + should_push = false; + } } } if should_push { - identifiers.push(IdWithIdentifier { + identifiers.push(MatchingRule::Simple(IdWithIdentifier { kind: identifier, id: id.clone(), matching_strategy: Option::from(MatchingStrategy::Legacy), - }); + })); } } SocketMessage::ManageFocusedWindow => { diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 67b233eb..3a8ee999 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -34,9 +34,11 @@ use color_eyre::Result; use crossbeam_channel::Receiver; use hotwatch::EventKind; use hotwatch::Hotwatch; +use komorebi_core::config_generation::ApplicationConfiguration; use komorebi_core::config_generation::ApplicationConfigurationGenerator; use komorebi_core::config_generation::ApplicationOptions; use komorebi_core::config_generation::IdWithIdentifier; +use komorebi_core::config_generation::MatchingRule; use komorebi_core::config_generation::MatchingStrategy; use komorebi_core::resolve_home_path; use komorebi_core::ApplicationIdentifier; @@ -269,22 +271,22 @@ pub struct StaticConfig { pub global_work_area_offset: Option, /// Individual window floating rules #[serde(skip_serializing_if = "Option::is_none")] - pub float_rules: Option>, + pub float_rules: Option>, /// Individual window force-manage rules #[serde(skip_serializing_if = "Option::is_none")] - pub manage_rules: Option>, + pub manage_rules: Option>, /// Identify border overflow applications #[serde(skip_serializing_if = "Option::is_none")] - pub border_overflow_applications: Option>, + pub border_overflow_applications: Option>, /// Identify tray and multi-window applications #[serde(skip_serializing_if = "Option::is_none")] - pub tray_and_multi_window_applications: Option>, + pub tray_and_multi_window_applications: Option>, /// Identify applications that have the WS_EX_LAYERED extended window style #[serde(skip_serializing_if = "Option::is_none")] - pub layered_applications: Option>, + pub layered_applications: Option>, /// Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare) #[serde(skip_serializing_if = "Option::is_none")] - pub object_name_change_applications: Option>, + pub object_name_change_applications: Option>, /// Set monitor index preferences #[serde(skip_serializing_if = "Option::is_none")] pub monitor_index_preferences: Option>, @@ -452,106 +454,40 @@ impl StaticConfig { let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock(); let mut layered_identifiers = LAYERED_WHITELIST.lock(); - if let Some(float) = &mut self.float_rules { - for identifier in float { - if identifier.matching_strategy.is_none() { - identifier.matching_strategy = Option::from(MatchingStrategy::Legacy); - } - - if !float_identifiers.contains(identifier) { - float_identifiers.push(identifier.clone()); - - if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) { - let re = Regex::new(&identifier.id)?; - regex_identifiers.insert(identifier.id.clone(), re); - } - } - } + if let Some(rules) = &mut self.float_rules { + populate_rules(rules, &mut float_identifiers, &mut regex_identifiers)?; } - if let Some(manage) = &mut self.manage_rules { - for identifier in manage { - if identifier.matching_strategy.is_none() { - identifier.matching_strategy = Option::from(MatchingStrategy::Legacy); - } - - if !manage_identifiers.contains(identifier) { - manage_identifiers.push(identifier.clone()); - - if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) { - let re = Regex::new(&identifier.id)?; - regex_identifiers.insert(identifier.id.clone(), re); - } - } - } + if let Some(rules) = &mut self.manage_rules { + populate_rules(rules, &mut manage_identifiers, &mut regex_identifiers)?; } - if let Some(identifiers) = &mut self.object_name_change_applications { - for identifier in identifiers { - if identifier.matching_strategy.is_none() { - identifier.matching_strategy = Option::from(MatchingStrategy::Legacy); - } - - if !object_name_change_identifiers.contains(identifier) { - object_name_change_identifiers.push(identifier.clone()); - - if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) { - let re = Regex::new(&identifier.id)?; - regex_identifiers.insert(identifier.id.clone(), re); - } - } - } + if let Some(rules) = &mut self.object_name_change_applications { + populate_rules( + rules, + &mut object_name_change_identifiers, + &mut regex_identifiers, + )?; } - if let Some(identifiers) = &mut self.layered_applications { - for identifier in identifiers { - if identifier.matching_strategy.is_none() { - identifier.matching_strategy = Option::from(MatchingStrategy::Legacy); - } - - if !layered_identifiers.contains(identifier) { - layered_identifiers.push(identifier.clone()); - - if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) { - let re = Regex::new(&identifier.id)?; - regex_identifiers.insert(identifier.id.clone(), re); - } - } - } + if let Some(rules) = &mut self.layered_applications { + populate_rules(rules, &mut layered_identifiers, &mut regex_identifiers)?; } - if let Some(identifiers) = &mut self.border_overflow_applications { - for identifier in identifiers { - if identifier.matching_strategy.is_none() { - identifier.matching_strategy = Option::from(MatchingStrategy::Legacy); - } - - if !border_overflow_identifiers.contains(identifier) { - border_overflow_identifiers.push(identifier.clone()); - - if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) { - let re = Regex::new(&identifier.id)?; - regex_identifiers.insert(identifier.id.clone(), re); - } - } - } + if let Some(rules) = &mut self.border_overflow_applications { + populate_rules( + rules, + &mut border_overflow_identifiers, + &mut regex_identifiers, + )?; } - if let Some(identifiers) = &mut self.tray_and_multi_window_applications { - for identifier in identifiers { - if identifier.matching_strategy.is_none() { - identifier.matching_strategy = Option::from(MatchingStrategy::Legacy); - } - - if !tray_and_multi_window_identifiers.contains(identifier) { - tray_and_multi_window_identifiers.push(identifier.clone()); - - if matches!(identifier.matching_strategy, Some(MatchingStrategy::Regex)) { - let re = Regex::new(&identifier.id)?; - regex_identifiers.insert(identifier.id.clone(), re); - } - } - } + if let Some(rules) = &mut self.tray_and_multi_window_applications { + populate_rules( + rules, + &mut tray_and_multi_window_identifiers, + &mut regex_identifiers, + )?; } if let Some(path) = &self.app_specific_configuration_path { @@ -560,120 +496,48 @@ impl StaticConfig { let asc = ApplicationConfigurationGenerator::load(&content)?; for mut entry in asc { - if let Some(float) = entry.float_identifiers { - for f in float { - let mut without_comment: IdWithIdentifier = f.into(); - if without_comment.matching_strategy.is_none() { - without_comment.matching_strategy = - Option::from(MatchingStrategy::Legacy); - } - - if !float_identifiers.contains(&without_comment) { - float_identifiers.push(without_comment.clone()); - - if matches!( - without_comment.matching_strategy, - Some(MatchingStrategy::Regex) - ) { - let re = Regex::new(&without_comment.id)?; - regex_identifiers.insert(without_comment.id.clone(), re); - } - } - } + if let Some(rules) = &mut entry.float_identifiers { + populate_rules(rules, &mut float_identifiers, &mut regex_identifiers)?; } - if let Some(options) = entry.options { + + if let Some(ref options) = entry.options { + let options = options.clone(); for o in options { match o { ApplicationOptions::ObjectNameChange => { - if entry.identifier.matching_strategy.is_none() { - entry.identifier.matching_strategy = - Option::from(MatchingStrategy::Legacy); - } - - if !object_name_change_identifiers.contains(&entry.identifier) { - object_name_change_identifiers.push(entry.identifier.clone()); - - if matches!( - entry.identifier.matching_strategy, - Some(MatchingStrategy::Regex) - ) { - let re = Regex::new(&entry.identifier.id)?; - regex_identifiers.insert(entry.identifier.id.clone(), re); - } - } + populate_option( + &mut entry, + &mut object_name_change_identifiers, + &mut regex_identifiers, + )?; } ApplicationOptions::Layered => { - if entry.identifier.matching_strategy.is_none() { - entry.identifier.matching_strategy = - Option::from(MatchingStrategy::Legacy); - } - - if !layered_identifiers.contains(&entry.identifier) { - layered_identifiers.push(entry.identifier.clone()); - - if matches!( - entry.identifier.matching_strategy, - Some(MatchingStrategy::Regex) - ) { - let re = Regex::new(&entry.identifier.id)?; - regex_identifiers.insert(entry.identifier.id.clone(), re); - } - } + populate_option( + &mut entry, + &mut layered_identifiers, + &mut regex_identifiers, + )?; } ApplicationOptions::BorderOverflow => { - if entry.identifier.matching_strategy.is_none() { - entry.identifier.matching_strategy = - Option::from(MatchingStrategy::Legacy); - } - - if !border_overflow_identifiers.contains(&entry.identifier) { - border_overflow_identifiers.push(entry.identifier.clone()); - - if matches!( - entry.identifier.matching_strategy, - Some(MatchingStrategy::Regex) - ) { - let re = Regex::new(&entry.identifier.id)?; - regex_identifiers.insert(entry.identifier.id.clone(), re); - } - } + populate_option( + &mut entry, + &mut border_overflow_identifiers, + &mut regex_identifiers, + )?; } ApplicationOptions::TrayAndMultiWindow => { - if entry.identifier.matching_strategy.is_none() { - entry.identifier.matching_strategy = - Option::from(MatchingStrategy::Legacy); - } - - if !tray_and_multi_window_identifiers.contains(&entry.identifier) { - tray_and_multi_window_identifiers - .push(entry.identifier.clone()); - - if matches!( - entry.identifier.matching_strategy, - Some(MatchingStrategy::Regex) - ) { - let re = Regex::new(&entry.identifier.id)?; - regex_identifiers.insert(entry.identifier.id.clone(), re); - } - } + populate_option( + &mut entry, + &mut tray_and_multi_window_identifiers, + &mut regex_identifiers, + )?; } ApplicationOptions::Force => { - if entry.identifier.matching_strategy.is_none() { - entry.identifier.matching_strategy = - Option::from(MatchingStrategy::Legacy); - } - - if !manage_identifiers.contains(&entry.identifier) { - manage_identifiers.push(entry.identifier.clone()); - - if matches!( - entry.identifier.matching_strategy, - Some(MatchingStrategy::Regex) - ) { - let re = Regex::new(&entry.identifier.id)?; - regex_identifiers.insert(entry.identifier.id.clone(), re); - } - } + populate_option( + &mut entry, + &mut manage_identifiers, + &mut regex_identifiers, + )?; } } } @@ -901,3 +765,67 @@ impl StaticConfig { Ok(()) } } + +fn populate_option( + entry: &mut ApplicationConfiguration, + identifiers: &mut Vec, + regex_identifiers: &mut HashMap, +) -> Result<()> { + if entry.identifier.matching_strategy.is_none() { + entry.identifier.matching_strategy = Option::from(MatchingStrategy::Legacy); + } + + let rule = MatchingRule::Simple(entry.identifier.clone()); + + if !identifiers.contains(&rule) { + identifiers.push(rule); + + if matches!( + entry.identifier.matching_strategy, + Some(MatchingStrategy::Regex) + ) { + let re = Regex::new(&entry.identifier.id)?; + regex_identifiers.insert(entry.identifier.id.clone(), re); + } + } + + Ok(()) +} + +fn populate_rules( + matching_rules: &mut Vec, + identifiers: &mut Vec, + regex_identifiers: &mut HashMap, +) -> Result<()> { + for matching_rule in matching_rules { + if !identifiers.contains(matching_rule) { + match matching_rule { + MatchingRule::Simple(simple) => { + if simple.matching_strategy.is_none() { + simple.matching_strategy = Option::from(MatchingStrategy::Legacy); + } + + if matches!(simple.matching_strategy, Some(MatchingStrategy::Regex)) { + let re = Regex::new(&simple.id)?; + regex_identifiers.insert(simple.id.clone(), re); + } + } + MatchingRule::Composite(composite) => { + for rule in composite { + if rule.matching_strategy.is_none() { + rule.matching_strategy = Option::from(MatchingStrategy::Legacy); + } + + if matches!(rule.matching_strategy, Some(MatchingStrategy::Regex)) { + let re = Regex::new(&rule.id)?; + regex_identifiers.insert(rule.id.clone(), re); + } + } + } + } + identifiers.push(matching_rule.clone()); + } + } + + Ok(()) +} diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 09198485..846404a7 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -10,6 +10,7 @@ use color_eyre::eyre; use color_eyre::eyre::anyhow; use color_eyre::Result; use komorebi_core::config_generation::IdWithIdentifier; +use komorebi_core::config_generation::MatchingRule; use komorebi_core::config_generation::MatchingStrategy; use regex::Regex; use schemars::JsonSchema; @@ -506,157 +507,203 @@ pub fn should_act( exe_name: &str, class: &str, path: &str, - identifiers: &[IdWithIdentifier], + identifiers: &[MatchingRule], regex_identifiers: &HashMap, ) -> bool { let mut should_act = false; for identifier in identifiers { - match identifier.matching_strategy { - None => { - panic!("there is no matching strategy identified for this rule"); + match identifier { + MatchingRule::Simple(identifier) => { + if should_act_individual( + title, + exe_name, + class, + path, + identifier, + regex_identifiers, + ) { + should_act = true + }; } - Some(MatchingStrategy::Legacy) => match identifier.kind { - ApplicationIdentifier::Title => { - if title.starts_with(&identifier.id) || title.ends_with(&identifier.id) { - should_act = true; - } + MatchingRule::Composite(identifiers) => { + let mut composite_results = vec![]; + for identifier in identifiers { + composite_results.push(should_act_individual( + title, + exe_name, + class, + path, + identifier, + regex_identifiers, + )); } - ApplicationIdentifier::Class => { - if class.starts_with(&identifier.id) || class.ends_with(&identifier.id) { - should_act = true; - } + + if composite_results.iter().all(|&x| x) { + should_act = true; } - ApplicationIdentifier::Exe => { - if exe_name.eq(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Path => { - if path.eq(&identifier.id) { - should_act = true; - } - } - }, - Some(MatchingStrategy::Equals) => match identifier.kind { - ApplicationIdentifier::Title => { - if title.eq(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Class => { - if class.eq(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Exe => { - if exe_name.eq(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Path => { - if path.eq(&identifier.id) { - should_act = true; - } - } - }, - Some(MatchingStrategy::StartsWith) => match identifier.kind { - ApplicationIdentifier::Title => { - if title.starts_with(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Class => { - if class.starts_with(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Exe => { - if exe_name.starts_with(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Path => { - if path.starts_with(&identifier.id) { - should_act = true; - } - } - }, - Some(MatchingStrategy::EndsWith) => match identifier.kind { - ApplicationIdentifier::Title => { - if title.ends_with(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Class => { - if class.ends_with(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Exe => { - if exe_name.ends_with(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Path => { - if path.ends_with(&identifier.id) { - should_act = true; - } - } - }, - Some(MatchingStrategy::Contains) => match identifier.kind { - ApplicationIdentifier::Title => { - if title.contains(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Class => { - if class.contains(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Exe => { - if exe_name.contains(&identifier.id) { - should_act = true; - } - } - ApplicationIdentifier::Path => { - if path.contains(&identifier.id) { - should_act = true; - } - } - }, - Some(MatchingStrategy::Regex) => match identifier.kind { - ApplicationIdentifier::Title => { - if let Some(re) = regex_identifiers.get(&identifier.id) { - if re.is_match(title) { - should_act = true; - } - } - } - ApplicationIdentifier::Class => { - if let Some(re) = regex_identifiers.get(&identifier.id) { - if re.is_match(class) { - should_act = true; - } - } - } - ApplicationIdentifier::Exe => { - if let Some(re) = regex_identifiers.get(&identifier.id) { - if re.is_match(exe_name) { - should_act = true; - } - } - } - ApplicationIdentifier::Path => { - if let Some(re) = regex_identifiers.get(&identifier.id) { - if re.is_match(path) { - should_act = true; - } - } - } - }, + } } } should_act } + +#[allow(clippy::cognitive_complexity, clippy::too_many_lines)] +pub fn should_act_individual( + title: &str, + exe_name: &str, + class: &str, + path: &str, + identifier: &IdWithIdentifier, + regex_identifiers: &HashMap, +) -> bool { + let mut should_act = false; + + match identifier.matching_strategy { + None => { + panic!("there is no matching strategy identified for this rule"); + } + Some(MatchingStrategy::Legacy) => match identifier.kind { + ApplicationIdentifier::Title => { + if title.starts_with(&identifier.id) || title.ends_with(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Class => { + if class.starts_with(&identifier.id) || class.ends_with(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Exe => { + if exe_name.eq(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Path => { + if path.eq(&identifier.id) { + should_act = true; + } + } + }, + Some(MatchingStrategy::Equals) => match identifier.kind { + ApplicationIdentifier::Title => { + if title.eq(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Class => { + if class.eq(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Exe => { + if exe_name.eq(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Path => { + if path.eq(&identifier.id) { + should_act = true; + } + } + }, + Some(MatchingStrategy::StartsWith) => match identifier.kind { + ApplicationIdentifier::Title => { + if title.starts_with(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Class => { + if class.starts_with(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Exe => { + if exe_name.starts_with(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Path => { + if path.starts_with(&identifier.id) { + should_act = true; + } + } + }, + Some(MatchingStrategy::EndsWith) => match identifier.kind { + ApplicationIdentifier::Title => { + if title.ends_with(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Class => { + if class.ends_with(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Exe => { + if exe_name.ends_with(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Path => { + if path.ends_with(&identifier.id) { + should_act = true; + } + } + }, + Some(MatchingStrategy::Contains) => match identifier.kind { + ApplicationIdentifier::Title => { + if title.contains(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Class => { + if class.contains(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Exe => { + if exe_name.contains(&identifier.id) { + should_act = true; + } + } + ApplicationIdentifier::Path => { + if path.contains(&identifier.id) { + should_act = true; + } + } + }, + Some(MatchingStrategy::Regex) => match identifier.kind { + ApplicationIdentifier::Title => { + if let Some(re) = regex_identifiers.get(&identifier.id) { + if re.is_match(title) { + should_act = true; + } + } + } + ApplicationIdentifier::Class => { + if let Some(re) = regex_identifiers.get(&identifier.id) { + if re.is_match(class) { + should_act = true; + } + } + } + ApplicationIdentifier::Exe => { + if let Some(re) = regex_identifiers.get(&identifier.id) { + if re.is_match(exe_name) { + should_act = true; + } + } + } + ApplicationIdentifier::Path => { + if let Some(re) = regex_identifiers.get(&identifier.id) { + if re.is_match(path) { + should_act = true; + } + } + } + }, + } + + should_act +} diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index f4366d4f..511045f3 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -21,7 +21,7 @@ use serde::Deserialize; use serde::Serialize; use uds_windows::UnixListener; -use komorebi_core::config_generation::IdWithIdentifier; +use komorebi_core::config_generation::MatchingRule; use komorebi_core::custom_layout::CustomLayout; use komorebi_core::Arrangement; use komorebi_core::Axis; @@ -97,12 +97,12 @@ pub struct State { pub mouse_follows_focus: bool, pub has_pending_raise_op: bool, pub remove_titlebars: bool, - pub float_identifiers: Vec, - pub manage_identifiers: Vec, - pub layered_whitelist: Vec, - pub tray_and_multi_window_identifiers: Vec, - pub border_overflow_identifiers: Vec, - pub name_change_on_launch_identifiers: Vec, + pub float_identifiers: Vec, + pub manage_identifiers: Vec, + pub layered_whitelist: Vec, + pub tray_and_multi_window_identifiers: Vec, + pub border_overflow_identifiers: Vec, + pub name_change_on_launch_identifiers: Vec, pub monitor_index_preferences: HashMap, pub display_index_preferences: HashMap, }