Skip to content

Commit

Permalink
feat(config): add support for v2 asc json
Browse files Browse the repository at this point in the history
This commit adds support for a v2 format of the application specific
configuration file, centralizing on JSON to maximize the knowledge
crossover for people already familiar with the types used in
komorebi.json.

The biggest difference besides the format change is that matchers must
be used explicitly for every kind of rule, rather than being able to
specify options on a default rule. This is a bit more verbose, but
ultimately allows for significantly more flexibility.
  • Loading branch information
LGUG2Z committed Oct 14, 2024
1 parent cbe5b24 commit b612066
Show file tree
Hide file tree
Showing 6 changed files with 375 additions and 76 deletions.
1 change: 1 addition & 0 deletions komorebi-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![warn(clippy::all)]
#![allow(clippy::missing_errors_doc)]

pub use komorebi::asc::ApplicationSpecificConfiguration;
pub use komorebi::colour::Colour;
pub use komorebi::colour::Rgb;
pub use komorebi::config_generation::ApplicationConfiguration;
Expand Down
135 changes: 135 additions & 0 deletions komorebi/src/core/asc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use crate::config_generation::ApplicationConfiguration;
use crate::config_generation::ApplicationOptions;
use crate::config_generation::MatchingRule;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
use std::ops::Deref;
use std::ops::DerefMut;
use std::path::PathBuf;

#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ApplicationSpecificConfiguration(pub BTreeMap<String, AscApplicationRulesOrSchema>);

#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum AscApplicationRulesOrSchema {
AscApplicationRules(AscApplicationRules),
Schema(String),
}

impl Deref for ApplicationSpecificConfiguration {
type Target = BTreeMap<String, AscApplicationRulesOrSchema>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for ApplicationSpecificConfiguration {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl ApplicationSpecificConfiguration {
pub fn load(pathbuf: &PathBuf) -> Result<Self> {
let content = std::fs::read_to_string(pathbuf)?;
Ok(serde_json::from_str(&content)?)
}

pub fn format(pathbuf: &PathBuf) -> Result<String> {
Ok(serde_json::to_string_pretty(&Self::load(pathbuf)?)?)
}
}

/// Rules that determine how an application is handled
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct AscApplicationRules {
/// Rules to ignore specific windows
#[serde(skip_serializing_if = "Option::is_none")]
pub ignore: Option<Vec<MatchingRule>>,
/// Rules to forcibly manage specific windows
#[serde(skip_serializing_if = "Option::is_none")]
pub manage: Option<Vec<MatchingRule>>,
/// Rules to manage specific windows as floating windows
#[serde(skip_serializing_if = "Option::is_none")]
pub floating: Option<Vec<MatchingRule>>,
/// Rules to ignore specific windows from the transparency feature
#[serde(skip_serializing_if = "Option::is_none")]
pub transparency_ignore: Option<Vec<MatchingRule>>,
/// Rules to identify applications which minimize to the tray or have multiple windows
#[serde(skip_serializing_if = "Option::is_none")]
pub tray_and_multi_window: Option<Vec<MatchingRule>>,
/// Rules to identify applications which have the `WS_EX_LAYERED` Extended Window Style
#[serde(skip_serializing_if = "Option::is_none")]
pub layered: Option<Vec<MatchingRule>>,
/// Rules to identify applications which send the `EVENT_OBJECT_NAMECHANGE` event on launch
#[serde(skip_serializing_if = "Option::is_none")]
pub object_name_change: Option<Vec<MatchingRule>>,
/// Rules to identify applications which are slow to send initial event notifications
#[serde(skip_serializing_if = "Option::is_none")]
pub slow_application: Option<Vec<MatchingRule>>,
}

impl From<Vec<ApplicationConfiguration>> for ApplicationSpecificConfiguration {
fn from(value: Vec<ApplicationConfiguration>) -> Self {
let mut map = BTreeMap::new();

for entry in &value {
let key = entry.name.clone();
let mut rules = AscApplicationRules {
ignore: None,
manage: None,
floating: None,
transparency_ignore: None,
tray_and_multi_window: None,
layered: None,
object_name_change: None,
slow_application: None,
};

rules.ignore = entry.ignore_identifiers.clone();

if let Some(options) = &entry.options {
for opt in options {
match opt {
ApplicationOptions::ObjectNameChange => {
rules.object_name_change =
Some(vec![MatchingRule::Simple(entry.identifier.clone())]);
}
ApplicationOptions::Layered => {
rules.layered =
Some(vec![MatchingRule::Simple(entry.identifier.clone())]);
}
ApplicationOptions::TrayAndMultiWindow => {
rules.tray_and_multi_window =
Some(vec![MatchingRule::Simple(entry.identifier.clone())]);
}
ApplicationOptions::Force => {
rules.manage =
Some(vec![MatchingRule::Simple(entry.identifier.clone())]);
}
ApplicationOptions::BorderOverflow => {}
}
}
}

if rules.ignore.is_some()
|| rules.manage.is_some()
|| rules.floating.is_some()
|| rules.transparency_ignore.is_some()
|| rules.tray_and_multi_window.is_some()
|| rules.layered.is_some()
|| rules.object_name_change.is_some()
|| rules.slow_application.is_some()
{
map.insert(key, AscApplicationRulesOrSchema::AscApplicationRules(rules));
}
}

Self(map)
}
}
1 change: 1 addition & 0 deletions komorebi/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub use rect::Rect;

pub mod animation;
pub mod arrangement;
pub mod asc;
pub mod config_generation;
pub mod custom_layout;
pub mod cycle_direction;
Expand Down
167 changes: 129 additions & 38 deletions komorebi/src/static_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
use crate::WINDOWS_11;
use crate::WORKSPACE_MATCHING_RULES;

use crate::asc::ApplicationSpecificConfiguration;
use crate::asc::AscApplicationRulesOrSchema;
use crate::config_generation::WorkspaceMatchingRule;
use crate::core::config_generation::ApplicationConfiguration;
use crate::core::config_generation::ApplicationConfigurationGenerator;
Expand Down Expand Up @@ -961,51 +963,140 @@ impl StaticConfig {
}

if let Some(path) = &self.app_specific_configuration_path {
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,
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,
)?;
}
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,
)?;

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
}
}
}
ApplicationOptions::Force => {
populate_option(
&mut entry,
&mut manage_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,
)?;
}
}
}
ApplicationOptions::BorderOverflow => {} // deprecated
}
}
}
_ => {}
},
}
}

Expand Down
Loading

0 comments on commit b612066

Please sign in to comment.