diff --git a/Cargo.lock b/Cargo.lock index 63326c9b2..cb1368178 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,9 +84,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "3.1.6" +version = "3.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" +checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c" dependencies = [ "atty", "bitflags", @@ -102,9 +102,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.1.4" +version = "3.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" +checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" dependencies = [ "heck", "proc-macro-error", @@ -505,6 +505,7 @@ dependencies = [ "powershell_script", "serde", "serde_json", + "serde_yaml", "uds_windows", "windows", ] @@ -1305,9 +1306,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c" +checksum = "90442985ee2f57c9e1b548ee72ae842f4a9a20e3f417cc38dbc5dc684d9bb4ee" dependencies = [ "lazy_static", "valuable", @@ -1336,9 +1337,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" +checksum = "b9df98b037d039d03400d9dd06b0f8ce05486b5f25e9a2d7d36196e142ebbc52" dependencies = [ "ansi_term", "lazy_static", diff --git a/komorebi-core/src/config_generation.rs b/komorebi-core/src/config_generation.rs new file mode 100644 index 000000000..8233f66e4 --- /dev/null +++ b/komorebi-core/src/config_generation.rs @@ -0,0 +1,119 @@ +use clap::ArgEnum; +use color_eyre::Result; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use strum::Display; +use strum::EnumString; + +use crate::ApplicationIdentifier; + +#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum ApplicationOptions { + ObjectNameChange, + Layered, + BorderOverflow, + TrayAndMultiWindow, + Force, +} + +impl ApplicationOptions { + #[must_use] + pub fn cfgen(&self, kind: &ApplicationIdentifier, id: &str) -> String { + format!( + "Run, {}, , Hide", + match self { + ApplicationOptions::ObjectNameChange => { + format!( + "komorebic.exe identify-object-name-change-application {} {}", + kind, id + ) + } + ApplicationOptions::Layered => { + format!("komorebic.exe identify-layered-application {} {}", kind, id) + } + ApplicationOptions::BorderOverflow => { + format!( + "komorebic.exe identify-border-overflow-application {} {}", + kind, id + ) + } + ApplicationOptions::TrayAndMultiWindow => { + format!("komorebic.exe identify-tray-application {} {}", kind, id) + } + ApplicationOptions::Force => { + format!("komorebic.exe manage-rule {} {}", kind, id) + } + } + ) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct IdWithIdentifier { + kind: ApplicationIdentifier, + id: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct ApplicationConfiguration { + name: String, + identifier: IdWithIdentifier, + options: Option>, + float_identifiers: Option>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +pub struct ApplicationConfigurationGenerator; + +impl ApplicationConfigurationGenerator { + fn load(content: &str) -> Result> { + Ok(serde_yaml::from_str(content)?) + } + + pub fn generate(content: &str) -> Result> { + let mut cfgen = Self::load(content)?; + cfgen.sort_by(|a, b| a.name.cmp(&b.name)); + + let mut lines = vec![ + String::from("; Generated by komorebic.exe"), + String::from("; To use this file, add the line below to the top of your komorebi.ahk configuration file"), + String::from("; #Include %A_ScriptDir%\\komorebi.generated.ahk"), + String::from("") + ]; + + let mut float_rules = vec![]; + + for app in cfgen { + lines.push(format!("; {}", app.name)); + if let Some(options) = app.options { + for opt in options { + if let ApplicationOptions::TrayAndMultiWindow = opt { + lines.push(String::from("; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line")); + } + lines.push(opt.cfgen(&app.identifier.kind, &app.identifier.id)); + } + } + + if let Some(float_identifiers) = app.float_identifiers { + for float in float_identifiers { + let float_rule = format!( + "Run, 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()); + lines.push(float_rule); + } + } + } + lines.push(String::from("")); + } + + Ok(lines) + } +} diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 085d3cd2b..2ed4cbf98 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -23,6 +23,7 @@ pub use operation_direction::OperationDirection; pub use rect::Rect; pub mod arrangement; +pub mod config_generation; pub mod custom_layout; pub mod cycle_direction; pub mod default_layout; @@ -138,6 +139,7 @@ pub enum StateQuery { #[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] pub enum ApplicationIdentifier { Exe, Class, diff --git a/komorebic.lib.sample.ahk b/komorebic.lib.sample.ahk index 77838a167..2f3a437c6 100644 --- a/komorebic.lib.sample.ahk +++ b/komorebic.lib.sample.ahk @@ -304,6 +304,10 @@ AhkLibrary() { Run, komorebic.exe ahk-library, , Hide } +ApplicationSpecificConfiguration(path) { + Run, komorebic.exe application-specific-configuration %path%, , Hide +} + NotificationSchema() { Run, komorebic.exe notification-schema, , Hide } \ No newline at end of file diff --git a/komorebic/Cargo.toml b/komorebic/Cargo.toml index 054ed2bca..d479441f0 100644 --- a/komorebic/Cargo.toml +++ b/komorebic/Cargo.toml @@ -24,6 +24,7 @@ paste = "1" powershell_script = "0.3" serde = { version = "1", features = ["derive"] } serde_json = "1" +serde_yaml = "0.8" uds_windows = "1" [dependencies.windows] diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 61e329e81..50b29fd7d 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -1,6 +1,7 @@ #![warn(clippy::all, clippy::nursery, clippy::pedantic)] #![allow(clippy::missing_errors_doc)] +use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::io::BufRead; @@ -28,6 +29,7 @@ use windows::Win32::UI::WindowsAndMessaging::SW_RESTORE; use derive_ahk::AhkFunction; use derive_ahk::AhkLibrary; +use komorebi_core::config_generation::ApplicationConfigurationGenerator; use komorebi_core::ApplicationIdentifier; use komorebi_core::Axis; use komorebi_core::CycleDirection; @@ -420,6 +422,12 @@ struct Unsubscribe { named_pipe: String, } +#[derive(Parser, AhkFunction)] +pub struct ApplicationSpecificConfiguration { + /// YAML file from which the application-specific configurations should be loaded + path: String, +} + #[derive(Parser)] #[clap(author, about, version, setting = AppSettings::DeriveDisplayOrder)] struct Opts { @@ -641,6 +649,10 @@ enum SubCommand { ToggleMouseFollowsFocus, /// Generate a library of AutoHotKey helper functions AhkLibrary, + /// Generate a collection of common application configurations to use in komorebi.ahk + #[clap(arg_required_else_help = true)] + #[clap(alias = "app-specific-configuration")] + ApplicationSpecificConfiguration(ApplicationSpecificConfiguration), /// Generate a JSON Schema of subscription notifications NotificationSchema, } @@ -1135,6 +1147,33 @@ fn main() -> Result<()> { SubCommand::WindowHidingBehaviour(arg) => { send_message(&*SocketMessage::WindowHidingBehaviour(arg.hiding_behaviour).as_bytes()?)?; } + SubCommand::ApplicationSpecificConfiguration(arg) => { + let content = fs::read_to_string(resolve_windows_path(&arg.path)?)?; + let lines = ApplicationConfigurationGenerator::generate(&content)?; + + let mut generated_config = HOME_DIR.clone(); + generated_config.push("komorebi.generated.ahk"); + let mut file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(generated_config.clone())?; + + file.write_all(lines.join("\n").as_bytes())?; + + println!( + "\nApplication-specific generated configuration written to {}", + generated_config.to_str().ok_or_else(|| anyhow!( + "could not find the path to the generated configuration file" + ))? + ); + + println!( + "\nYou can include the generated configuration at the top of your komorebi.ahk config with this line:" + ); + + println!("\n#Include %A_ScriptDir%\\komorebi.generated.ahk"); + } SubCommand::NotificationSchema => { let home = HOME_DIR.clone(); let mut socket = home;