feat(config): allow multiple asc files

This commit allows either the single canonical applications.json file,
or multiple files which adhere to the asc scheme to be given to the
app_specific_configuration_path config option.

I thought I had already implemented this earlier, but evidently I
hadn't.

This will be useful for people who want to maintain their own
independent set of asc rules, as they can be kept in a dedicated file
which won't be overwritten by the fetch-asc command.

resolve #736
This commit is contained in:
LGUG2Z
2025-03-13 19:35:12 -07:00
parent 3618beb366
commit fe9a1416e7
4 changed files with 194 additions and 137 deletions
+1
View File
@@ -52,6 +52,7 @@ pub use komorebi::workspace::Workspace;
pub use komorebi::workspace::WorkspaceGlobals; pub use komorebi::workspace::WorkspaceGlobals;
pub use komorebi::workspace::WorkspaceLayer; pub use komorebi::workspace::WorkspaceLayer;
pub use komorebi::AnimationsConfig; pub use komorebi::AnimationsConfig;
pub use komorebi::AppSpecificConfigurationPath;
pub use komorebi::AspectRatio; pub use komorebi::AspectRatio;
pub use komorebi::BorderColours; pub use komorebi::BorderColours;
pub use komorebi::CrossBoundaryBehaviour; pub use komorebi::CrossBoundaryBehaviour;
+177 -135
View File
@@ -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<PathBuf>),
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
/// The `komorebi.json` static configuration file reference for `v0.1.35` /// The `komorebi.json` static configuration file reference for `v0.1.35`
@@ -342,7 +352,7 @@ pub struct StaticConfig {
pub mouse_follows_focus: Option<bool>, pub mouse_follows_focus: Option<bool>,
/// Path to applications.json from komorebi-application-specific-configurations (default: None) /// Path to applications.json from komorebi-application-specific-configurations (default: None)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub app_specific_configuration_path: Option<PathBuf>, pub app_specific_configuration_path: Option<AppSpecificConfigurationPath>,
/// Width of the window border (default: 8) /// Width of the window border (default: 8)
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_width")] #[serde(alias = "active_window_border_width")]
@@ -1004,140 +1014,35 @@ impl StaticConfig {
} }
if let Some(path) = &self.app_specific_configuration_path { if let Some(path) = &self.app_specific_configuration_path {
match path.extension() { match path {
None => {} AppSpecificConfigurationPath::Single(path) => handle_asc_file(
Some(ext) => match ext.to_string_lossy().to_string().as_str() { path,
"yaml" => { &mut ignore_identifiers,
tracing::info!("loading applications.yaml from: {}", path.display()); &mut object_name_change_identifiers,
let path = resolve_home_path(path)?; &mut layered_identifiers,
let content = std::fs::read_to_string(path)?; &mut tray_and_multi_window_identifiers,
let asc = ApplicationConfigurationGenerator::load(&content)?; &mut manage_identifiers,
&mut floating_applications,
for mut entry in asc { &mut transparency_blacklist,
if let Some(rules) = &mut entry.ignore_identifiers { &mut slow_application_identifiers,
populate_rules( &mut regex_identifiers,
rules, )?,
&mut ignore_identifiers, AppSpecificConfigurationPath::Multiple(paths) => {
&mut regex_identifiers, for path in paths {
)?; handle_asc_file(
} path,
&mut ignore_identifiers,
if let Some(ref options) = entry.options { &mut object_name_change_identifiers,
let options = options.clone(); &mut layered_identifiers,
for o in options { &mut tray_and_multi_window_identifiers,
match o { &mut manage_identifiers,
ApplicationOptions::ObjectNameChange => { &mut floating_applications,
populate_option( &mut transparency_blacklist,
&mut entry, &mut slow_application_identifiers,
&mut object_name_change_identifiers, &mut regex_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
}
}
}
}
} }
"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)?; let mut value: Self = serde_json::from_str(&content)?;
if let Some(path) = &mut value.app_specific_configuration_path { 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 { if let Some(monitors) = &mut value.monitors {
@@ -1722,6 +1636,134 @@ fn populate_rules(
Ok(()) Ok(())
} }
#[allow(clippy::too_many_arguments)]
fn handle_asc_file(
path: &PathBuf,
ignore_identifiers: &mut Vec<MatchingRule>,
object_name_change_identifiers: &mut Vec<MatchingRule>,
layered_identifiers: &mut Vec<MatchingRule>,
tray_and_multi_window_identifiers: &mut Vec<MatchingRule>,
manage_identifiers: &mut Vec<MatchingRule>,
floating_applications: &mut Vec<MatchingRule>,
transparency_blacklist: &mut Vec<MatchingRule>,
slow_application_identifiers: &mut Vec<MatchingRule>,
regex_identifiers: &mut HashMap<String, Regex>,
) -> 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)] #[cfg(test)]
mod tests { mod tests {
use crate::StaticConfig; use crate::StaticConfig;
+3 -1
View File
@@ -25,6 +25,7 @@ use fs_tail::TailedFile;
use komorebi_client::resolve_home_path; use komorebi_client::resolve_home_path;
use komorebi_client::send_message; use komorebi_client::send_message;
use komorebi_client::send_query; use komorebi_client::send_query;
use komorebi_client::AppSpecificConfigurationPath;
use komorebi_client::ApplicationSpecificConfiguration; use komorebi_client::ApplicationSpecificConfiguration;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use miette::NamedSource; use miette::NamedSource;
@@ -1660,11 +1661,12 @@ fn main() -> Result<()> {
None => { None => {
println!("Application specific configuration file path has not been set. Try running 'komorebic fetch-asc'\n"); 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)) { if !Path::exists(Path::new(&path)) {
println!("Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", path.display()); println!("Application specific configuration file path '{}' does not exist. Try running 'komorebic fetch-asc'\n", path.display());
} }
} }
_ => {}
} }
} }
+13 -1
View File
@@ -131,7 +131,19 @@
}, },
"app_specific_configuration_path": { "app_specific_configuration_path": {
"description": "Path to applications.json from komorebi-application-specific-configurations (default: None)", "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": { "bar_configurations": {
"description": "Komorebi status bar configuration files for multiple instances on different monitors", "description": "Komorebi status bar configuration files for multiple instances on different monitors",