diff --git a/komorebi-client/src/lib.rs b/komorebi-client/src/lib.rs index 4614fc20..edac9b9a 100644 --- a/komorebi-client/src/lib.rs +++ b/komorebi-client/src/lib.rs @@ -52,6 +52,7 @@ pub use komorebi::workspace::Workspace; pub use komorebi::workspace::WorkspaceGlobals; pub use komorebi::workspace::WorkspaceLayer; pub use komorebi::AnimationsConfig; +pub use komorebi::AppSpecificConfigurationPath; pub use komorebi::AspectRatio; pub use komorebi::BorderColours; pub use komorebi::CrossBoundaryBehaviour; diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 51fea21c..06c674c7 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -302,6 +302,16 @@ impl From<&Monitor> for MonitorConfig { } } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[serde(untagged)] +pub enum AppSpecificConfigurationPath { + /// A single applications.json file + Single(PathBuf), + /// Multiple applications.json files + Multiple(Vec), +} + #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] /// The `komorebi.json` static configuration file reference for `v0.1.35` @@ -342,7 +352,7 @@ pub struct StaticConfig { pub mouse_follows_focus: Option, /// Path to applications.json from komorebi-application-specific-configurations (default: None) #[serde(skip_serializing_if = "Option::is_none")] - pub app_specific_configuration_path: Option, + pub app_specific_configuration_path: Option, /// Width of the window border (default: 8) #[serde(skip_serializing_if = "Option::is_none")] #[serde(alias = "active_window_border_width")] @@ -1004,140 +1014,35 @@ impl StaticConfig { } if let Some(path) = &self.app_specific_configuration_path { - match path.extension() { - None => {} - Some(ext) => match ext.to_string_lossy().to_string().as_str() { - "yaml" => { - tracing::info!("loading applications.yaml from: {}", path.display()); - let path = resolve_home_path(path)?; - let content = std::fs::read_to_string(path)?; - let asc = ApplicationConfigurationGenerator::load(&content)?; - - for mut entry in asc { - if let Some(rules) = &mut entry.ignore_identifiers { - populate_rules( - rules, - &mut ignore_identifiers, - &mut regex_identifiers, - )?; - } - - if let Some(ref options) = entry.options { - let options = options.clone(); - for o in options { - match o { - ApplicationOptions::ObjectNameChange => { - populate_option( - &mut entry, - &mut object_name_change_identifiers, - &mut regex_identifiers, - )?; - } - ApplicationOptions::Layered => { - populate_option( - &mut entry, - &mut layered_identifiers, - &mut regex_identifiers, - )?; - } - ApplicationOptions::TrayAndMultiWindow => { - populate_option( - &mut entry, - &mut tray_and_multi_window_identifiers, - &mut regex_identifiers, - )?; - } - ApplicationOptions::Force => { - populate_option( - &mut entry, - &mut manage_identifiers, - &mut regex_identifiers, - )?; - } - ApplicationOptions::BorderOverflow => {} // deprecated - } - } - } - } + match path { + AppSpecificConfigurationPath::Single(path) => handle_asc_file( + path, + &mut ignore_identifiers, + &mut object_name_change_identifiers, + &mut layered_identifiers, + &mut tray_and_multi_window_identifiers, + &mut manage_identifiers, + &mut floating_applications, + &mut transparency_blacklist, + &mut slow_application_identifiers, + &mut regex_identifiers, + )?, + AppSpecificConfigurationPath::Multiple(paths) => { + for path in paths { + handle_asc_file( + path, + &mut ignore_identifiers, + &mut object_name_change_identifiers, + &mut layered_identifiers, + &mut tray_and_multi_window_identifiers, + &mut manage_identifiers, + &mut floating_applications, + &mut transparency_blacklist, + &mut slow_application_identifiers, + &mut regex_identifiers, + )? } - "json" => { - tracing::info!("loading applications.json from: {}", path.display()); - let path = resolve_home_path(path)?; - let mut asc = ApplicationSpecificConfiguration::load(&path)?; - - for entry in asc.values_mut() { - match entry { - AscApplicationRulesOrSchema::Schema(_) => {} - AscApplicationRulesOrSchema::AscApplicationRules(entry) => { - if let Some(rules) = &mut entry.ignore { - populate_rules( - rules, - &mut ignore_identifiers, - &mut regex_identifiers, - )?; - } - - if let Some(rules) = &mut entry.manage { - populate_rules( - rules, - &mut manage_identifiers, - &mut regex_identifiers, - )?; - } - - if let Some(rules) = &mut entry.floating { - populate_rules( - rules, - &mut floating_applications, - &mut regex_identifiers, - )?; - } - - if let Some(rules) = &mut entry.transparency_ignore { - populate_rules( - rules, - &mut transparency_blacklist, - &mut regex_identifiers, - )?; - } - - if let Some(rules) = &mut entry.tray_and_multi_window { - populate_rules( - rules, - &mut tray_and_multi_window_identifiers, - &mut regex_identifiers, - )?; - } - - if let Some(rules) = &mut entry.layered { - populate_rules( - rules, - &mut layered_identifiers, - &mut regex_identifiers, - )?; - } - - if let Some(rules) = &mut entry.object_name_change { - populate_rules( - rules, - &mut object_name_change_identifiers, - &mut regex_identifiers, - )?; - } - - if let Some(rules) = &mut entry.slow_application { - populate_rules( - rules, - &mut slow_application_identifiers, - &mut regex_identifiers, - )?; - } - } - } - } - } - _ => {} - }, + } } } @@ -1153,7 +1058,16 @@ impl StaticConfig { let mut value: Self = serde_json::from_str(&content)?; if let Some(path) = &mut value.app_specific_configuration_path { - *path = resolve_home_path(&*path)?; + match path { + AppSpecificConfigurationPath::Single(path) => { + *path = resolve_home_path(&*path)?; + } + AppSpecificConfigurationPath::Multiple(paths) => { + for path in paths { + *path = resolve_home_path(&*path)?; + } + } + } } if let Some(monitors) = &mut value.monitors { @@ -1722,6 +1636,134 @@ fn populate_rules( Ok(()) } +#[allow(clippy::too_many_arguments)] +fn handle_asc_file( + path: &PathBuf, + ignore_identifiers: &mut Vec, + object_name_change_identifiers: &mut Vec, + layered_identifiers: &mut Vec, + tray_and_multi_window_identifiers: &mut Vec, + manage_identifiers: &mut Vec, + floating_applications: &mut Vec, + transparency_blacklist: &mut Vec, + slow_application_identifiers: &mut Vec, + regex_identifiers: &mut HashMap, +) -> Result<()> { + match path.extension() { + None => {} + Some(ext) => match ext.to_string_lossy().to_string().as_str() { + "yaml" => { + tracing::info!("loading applications.yaml from: {}", path.display()); + let path = resolve_home_path(path)?; + let content = std::fs::read_to_string(path)?; + let asc = ApplicationConfigurationGenerator::load(&content)?; + + for mut entry in asc { + if let Some(rules) = &mut entry.ignore_identifiers { + populate_rules(rules, ignore_identifiers, regex_identifiers)?; + } + + if let Some(ref options) = entry.options { + let options = options.clone(); + for o in options { + match o { + ApplicationOptions::ObjectNameChange => { + populate_option( + &mut entry, + object_name_change_identifiers, + regex_identifiers, + )?; + } + ApplicationOptions::Layered => { + populate_option( + &mut entry, + layered_identifiers, + regex_identifiers, + )?; + } + ApplicationOptions::TrayAndMultiWindow => { + populate_option( + &mut entry, + tray_and_multi_window_identifiers, + regex_identifiers, + )?; + } + ApplicationOptions::Force => { + populate_option( + &mut entry, + manage_identifiers, + regex_identifiers, + )?; + } + ApplicationOptions::BorderOverflow => {} // deprecated + } + } + } + } + } + "json" => { + tracing::info!("loading applications.json from: {}", path.display()); + let path = resolve_home_path(path)?; + let mut asc = ApplicationSpecificConfiguration::load(&path)?; + + for entry in asc.values_mut() { + match entry { + AscApplicationRulesOrSchema::Schema(_) => {} + AscApplicationRulesOrSchema::AscApplicationRules(entry) => { + if let Some(rules) = &mut entry.ignore { + populate_rules(rules, ignore_identifiers, regex_identifiers)?; + } + + if let Some(rules) = &mut entry.manage { + populate_rules(rules, manage_identifiers, regex_identifiers)?; + } + + if let Some(rules) = &mut entry.floating { + populate_rules(rules, floating_applications, regex_identifiers)?; + } + + if let Some(rules) = &mut entry.transparency_ignore { + populate_rules(rules, transparency_blacklist, regex_identifiers)?; + } + + if let Some(rules) = &mut entry.tray_and_multi_window { + populate_rules( + rules, + tray_and_multi_window_identifiers, + regex_identifiers, + )?; + } + + if let Some(rules) = &mut entry.layered { + populate_rules(rules, layered_identifiers, regex_identifiers)?; + } + + if let Some(rules) = &mut entry.object_name_change { + populate_rules( + rules, + object_name_change_identifiers, + regex_identifiers, + )?; + } + + if let Some(rules) = &mut entry.slow_application { + populate_rules( + rules, + slow_application_identifiers, + regex_identifiers, + )?; + } + } + } + } + } + _ => {} + }, + } + + Ok(()) +} + #[cfg(test)] mod tests { use crate::StaticConfig; diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 5191a3f7..185d6946 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -25,6 +25,7 @@ use fs_tail::TailedFile; use komorebi_client::resolve_home_path; use komorebi_client::send_message; use komorebi_client::send_query; +use komorebi_client::AppSpecificConfigurationPath; use komorebi_client::ApplicationSpecificConfiguration; use lazy_static::lazy_static; use miette::NamedSource; @@ -1660,11 +1661,12 @@ fn main() -> Result<()> { None => { println!("Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n"); } - Some(path) => { + Some(AppSpecificConfigurationPath::Single(path)) => { if !Path::exists(Path::new(&path)) { println!("Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", path.display()); } } + _ => {} } } diff --git a/schema.json b/schema.json index eb77b951..26f6d345 100644 --- a/schema.json +++ b/schema.json @@ -131,7 +131,19 @@ }, "app_specific_configuration_path": { "description": "Path to applications.json from komorebi-application-specific-configurations (default: None)", - "type": "string" + "anyOf": [ + { + "description": "A single applications.json file", + "type": "string" + }, + { + "description": "Multiple applications.json files", + "type": "array", + "items": { + "type": "string" + } + } + ] }, "bar_configurations": { "description": "Komorebi status bar configuration files for multiple instances on different monitors",