mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-03-26 19:31:16 +01:00
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
1799 lines
76 KiB
Rust
1799 lines
76 KiB
Rust
use crate::animation::PerAnimationPrefixConfig;
|
|
use crate::animation::ANIMATION_DURATION_GLOBAL;
|
|
use crate::animation::ANIMATION_DURATION_PER_ANIMATION;
|
|
use crate::animation::ANIMATION_ENABLED_GLOBAL;
|
|
use crate::animation::ANIMATION_ENABLED_PER_ANIMATION;
|
|
use crate::animation::ANIMATION_FPS;
|
|
use crate::animation::ANIMATION_STYLE_GLOBAL;
|
|
use crate::animation::ANIMATION_STYLE_PER_ANIMATION;
|
|
use crate::animation::DEFAULT_ANIMATION_FPS;
|
|
use crate::asc::ApplicationSpecificConfiguration;
|
|
use crate::asc::AscApplicationRulesOrSchema;
|
|
use crate::border_manager;
|
|
use crate::border_manager::ZOrder;
|
|
use crate::border_manager::IMPLEMENTATION;
|
|
use crate::border_manager::STYLE;
|
|
use crate::colour::Colour;
|
|
use crate::config_generation::WorkspaceMatchingRule;
|
|
use crate::core::config_generation::ApplicationConfiguration;
|
|
use crate::core::config_generation::ApplicationConfigurationGenerator;
|
|
use crate::core::config_generation::ApplicationOptions;
|
|
use crate::core::config_generation::MatchingRule;
|
|
use crate::core::config_generation::MatchingStrategy;
|
|
use crate::core::resolve_home_path;
|
|
use crate::core::AnimationStyle;
|
|
use crate::core::BorderImplementation;
|
|
use crate::core::BorderStyle;
|
|
use crate::core::DefaultLayout;
|
|
use crate::core::FocusFollowsMouseImplementation;
|
|
use crate::core::HidingBehaviour;
|
|
use crate::core::Layout;
|
|
use crate::core::MoveBehaviour;
|
|
use crate::core::OperationBehaviour;
|
|
use crate::core::Rect;
|
|
use crate::core::SocketMessage;
|
|
use crate::core::StackbarLabel;
|
|
use crate::core::StackbarMode;
|
|
use crate::core::WindowContainerBehaviour;
|
|
use crate::core::WindowManagementBehaviour;
|
|
use crate::current_virtual_desktop;
|
|
use crate::monitor;
|
|
use crate::monitor::Monitor;
|
|
use crate::monitor_reconciliator;
|
|
use crate::ring::Ring;
|
|
use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR;
|
|
use crate::stackbar_manager::STACKBAR_FONT_FAMILY;
|
|
use crate::stackbar_manager::STACKBAR_FONT_SIZE;
|
|
use crate::stackbar_manager::STACKBAR_LABEL;
|
|
use crate::stackbar_manager::STACKBAR_MODE;
|
|
use crate::stackbar_manager::STACKBAR_TAB_BACKGROUND_COLOUR;
|
|
use crate::stackbar_manager::STACKBAR_TAB_HEIGHT;
|
|
use crate::stackbar_manager::STACKBAR_TAB_WIDTH;
|
|
use crate::stackbar_manager::STACKBAR_UNFOCUSED_TEXT_COLOUR;
|
|
use crate::theme_manager;
|
|
use crate::transparency_manager;
|
|
use crate::window;
|
|
use crate::window_manager::WindowManager;
|
|
use crate::window_manager_event::WindowManagerEvent;
|
|
use crate::windows_api::WindowsApi;
|
|
use crate::workspace::Workspace;
|
|
use crate::AspectRatio;
|
|
use crate::Axis;
|
|
use crate::CrossBoundaryBehaviour;
|
|
use crate::PredefinedAspectRatio;
|
|
use crate::DATA_DIR;
|
|
use crate::DEFAULT_CONTAINER_PADDING;
|
|
use crate::DEFAULT_WORKSPACE_PADDING;
|
|
use crate::DISPLAY_INDEX_PREFERENCES;
|
|
use crate::FLOATING_APPLICATIONS;
|
|
use crate::FLOATING_WINDOW_TOGGLE_ASPECT_RATIO;
|
|
use crate::HIDING_BEHAVIOUR;
|
|
use crate::IGNORE_IDENTIFIERS;
|
|
use crate::LAYERED_WHITELIST;
|
|
use crate::MANAGE_IDENTIFIERS;
|
|
use crate::MONITOR_INDEX_PREFERENCES;
|
|
use crate::NO_TITLEBAR;
|
|
use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
|
use crate::OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST;
|
|
use crate::REGEX_IDENTIFIERS;
|
|
use crate::SLOW_APPLICATION_COMPENSATION_TIME;
|
|
use crate::SLOW_APPLICATION_IDENTIFIERS;
|
|
use crate::TRANSPARENCY_BLACKLIST;
|
|
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
|
use crate::WINDOWS_11;
|
|
use crate::WORKSPACE_MATCHING_RULES;
|
|
use color_eyre::Result;
|
|
use crossbeam_channel::Receiver;
|
|
use hotwatch::EventKind;
|
|
use hotwatch::Hotwatch;
|
|
use parking_lot::Mutex;
|
|
use regex::Regex;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use std::collections::HashMap;
|
|
use std::collections::HashSet;
|
|
use std::io::ErrorKind;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
use std::sync::atomic::Ordering;
|
|
use std::sync::Arc;
|
|
use uds_windows::UnixListener;
|
|
use uds_windows::UnixStream;
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub struct BorderColours {
|
|
/// Border colour when the container contains a single window
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub single: Option<Colour>,
|
|
/// Border colour when the container contains multiple windows
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub stack: Option<Colour>,
|
|
/// Border colour when the container is in monocle mode
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub monocle: Option<Colour>,
|
|
/// Border colour when the container is in floating mode
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub floating: Option<Colour>,
|
|
/// Border colour when the container is unfocused
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub unfocused: Option<Colour>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub struct WorkspaceConfig {
|
|
/// Name
|
|
pub name: String,
|
|
/// Layout (default: BSP)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub layout: Option<DefaultLayout>,
|
|
/// END OF LIFE FEATURE: Custom Layout (default: None)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub custom_layout: Option<PathBuf>,
|
|
/// Layout rules in the format of threshold => layout (default: None)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub layout_rules: Option<HashMap<usize, DefaultLayout>>,
|
|
/// END OF LIFE FEATURE: Custom layout rules (default: None)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub custom_layout_rules: Option<HashMap<usize, PathBuf>>,
|
|
/// Container padding (default: global)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub container_padding: Option<i32>,
|
|
/// Workspace padding (default: global)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub workspace_padding: Option<i32>,
|
|
/// Initial workspace application rules
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub initial_workspace_rules: Option<Vec<MatchingRule>>,
|
|
/// Permanent workspace application rules
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub workspace_rules: Option<Vec<MatchingRule>>,
|
|
/// Apply this monitor's window-based work area offset (default: true)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub apply_window_based_work_area_offset: Option<bool>,
|
|
/// Determine what happens when a new window is opened (default: Create)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub window_container_behaviour: Option<WindowContainerBehaviour>,
|
|
/// Window container behaviour rules in the format of threshold => behaviour (default: None)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub window_container_behaviour_rules: Option<HashMap<usize, WindowContainerBehaviour>>,
|
|
/// Enable or disable float override, which makes it so every new window opens in floating mode (default: false)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub float_override: Option<bool>,
|
|
/// Specify an axis on which to flip the selected layout (default: None)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub layout_flip: Option<Axis>,
|
|
}
|
|
|
|
impl From<&Workspace> for WorkspaceConfig {
|
|
fn from(value: &Workspace) -> Self {
|
|
let mut layout_rules = HashMap::new();
|
|
for (threshold, layout) in value.layout_rules() {
|
|
match layout {
|
|
Layout::Default(value) => {
|
|
layout_rules.insert(*threshold, *value);
|
|
}
|
|
Layout::Custom(_) => {}
|
|
}
|
|
}
|
|
let layout_rules = (!layout_rules.is_empty()).then_some(layout_rules);
|
|
|
|
let mut window_container_behaviour_rules = HashMap::new();
|
|
for (threshold, behaviour) in value.window_container_behaviour_rules().iter().flatten() {
|
|
window_container_behaviour_rules.insert(*threshold, *behaviour);
|
|
}
|
|
|
|
let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);
|
|
let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst);
|
|
|
|
let container_padding = value.container_padding().and_then(|container_padding| {
|
|
if container_padding == default_container_padding {
|
|
None
|
|
} else {
|
|
Option::from(container_padding)
|
|
}
|
|
});
|
|
|
|
let workspace_padding = value.workspace_padding().and_then(|workspace_padding| {
|
|
if workspace_padding == default_workspace_padding {
|
|
None
|
|
} else {
|
|
Option::from(workspace_padding)
|
|
}
|
|
});
|
|
|
|
Self {
|
|
name: value
|
|
.name()
|
|
.clone()
|
|
.unwrap_or_else(|| String::from("unnamed")),
|
|
layout: value
|
|
.tile()
|
|
.then_some(match value.layout() {
|
|
Layout::Default(layout) => Option::from(*layout),
|
|
Layout::Custom(_) => None,
|
|
})
|
|
.flatten(),
|
|
custom_layout: value
|
|
.workspace_config()
|
|
.as_ref()
|
|
.and_then(|c| c.custom_layout.clone()),
|
|
layout_rules,
|
|
custom_layout_rules: value
|
|
.workspace_config()
|
|
.as_ref()
|
|
.and_then(|c| c.custom_layout_rules.clone()),
|
|
container_padding,
|
|
workspace_padding,
|
|
initial_workspace_rules: value
|
|
.workspace_config()
|
|
.as_ref()
|
|
.and_then(|c| c.initial_workspace_rules.clone()),
|
|
workspace_rules: value
|
|
.workspace_config()
|
|
.as_ref()
|
|
.and_then(|c| c.workspace_rules.clone()),
|
|
apply_window_based_work_area_offset: Some(value.apply_window_based_work_area_offset()),
|
|
window_container_behaviour: *value.window_container_behaviour(),
|
|
window_container_behaviour_rules: Option::from(window_container_behaviour_rules),
|
|
float_override: *value.float_override(),
|
|
layout_flip: value.layout_flip(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub struct MonitorConfig {
|
|
/// Workspace configurations
|
|
pub workspaces: Vec<WorkspaceConfig>,
|
|
/// Monitor-specific work area offset (default: None)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub work_area_offset: Option<Rect>,
|
|
/// Window based work area offset (default: None)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub window_based_work_area_offset: Option<Rect>,
|
|
/// Open window limit after which the window based work area offset will no longer be applied (default: 1)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub window_based_work_area_offset_limit: Option<isize>,
|
|
/// Container padding (default: global)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub container_padding: Option<i32>,
|
|
/// Workspace padding (default: global)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub workspace_padding: Option<i32>,
|
|
}
|
|
|
|
impl From<&Monitor> for MonitorConfig {
|
|
fn from(value: &Monitor) -> Self {
|
|
let mut workspaces = vec![];
|
|
for w in value.workspaces() {
|
|
workspaces.push(WorkspaceConfig::from(w));
|
|
}
|
|
|
|
let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst);
|
|
let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst);
|
|
|
|
let container_padding = value.container_padding().and_then(|container_padding| {
|
|
if container_padding == default_container_padding {
|
|
None
|
|
} else {
|
|
Option::from(container_padding)
|
|
}
|
|
});
|
|
|
|
let workspace_padding = value.workspace_padding().and_then(|workspace_padding| {
|
|
if workspace_padding == default_workspace_padding {
|
|
None
|
|
} else {
|
|
Option::from(workspace_padding)
|
|
}
|
|
});
|
|
|
|
Self {
|
|
workspaces,
|
|
work_area_offset: value.work_area_offset(),
|
|
window_based_work_area_offset: value.window_based_work_area_offset(),
|
|
window_based_work_area_offset_limit: Some(value.window_based_work_area_offset_limit()),
|
|
container_padding,
|
|
workspace_padding,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
/// The `komorebi.json` static configuration file reference for `v0.1.35`
|
|
pub struct StaticConfig {
|
|
/// DEPRECATED from v0.1.22: no longer required
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub invisible_borders: Option<Rect>,
|
|
/// DISCOURAGED: Minimum width for a window to be eligible for tiling
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub minimum_window_width: Option<i32>,
|
|
/// DISCOURAGED: Minimum height for a window to be eligible for tiling
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub minimum_window_height: Option<i32>,
|
|
/// Delta to resize windows by (default 50)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub resize_delta: Option<i32>,
|
|
/// Determine what happens when a new window is opened (default: Create)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub window_container_behaviour: Option<WindowContainerBehaviour>,
|
|
/// Enable or disable float override, which makes it so every new window opens in floating mode
|
|
/// (default: false)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub float_override: Option<bool>,
|
|
/// Determine what happens when a window is moved across a monitor boundary (default: Swap)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub cross_monitor_move_behaviour: Option<MoveBehaviour>,
|
|
/// Determine what happens when an action is called on a window at a monitor boundary (default: Monitor)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub cross_boundary_behaviour: Option<CrossBoundaryBehaviour>,
|
|
/// Determine what happens when commands are sent while an unmanaged window is in the foreground (default: Op)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub unmanaged_window_operation_behaviour: Option<OperationBehaviour>,
|
|
/// END OF LIFE FEATURE: Use https://github.com/LGUG2Z/masir instead
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
|
|
/// Enable or disable mouse follows focus (default: true)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub mouse_follows_focus: Option<bool>,
|
|
/// Path to applications.json from komorebi-application-specific-configurations (default: None)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub app_specific_configuration_path: Option<AppSpecificConfigurationPath>,
|
|
/// Width of the window border (default: 8)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[serde(alias = "active_window_border_width")]
|
|
pub border_width: Option<i32>,
|
|
/// Offset of the window border (default: -1)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[serde(alias = "active_window_border_offset")]
|
|
pub border_offset: Option<i32>,
|
|
/// Display an active window border (default: false)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[serde(alias = "active_window_border")]
|
|
pub border: Option<bool>,
|
|
/// Active window border colours for different container types
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[serde(alias = "active_window_border_colours")]
|
|
pub border_colours: Option<BorderColours>,
|
|
/// Active window border style (default: System)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[serde(alias = "active_window_border_style")]
|
|
pub border_style: Option<BorderStyle>,
|
|
/// DEPRECATED from v0.1.31: no longer required
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub border_z_order: Option<ZOrder>,
|
|
/// Active window border implementation (default: Komorebi)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub border_implementation: Option<BorderImplementation>,
|
|
/// Add transparency to unfocused windows (default: false)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub transparency: Option<bool>,
|
|
/// Alpha value for unfocused window transparency [[0-255]] (default: 200)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub transparency_alpha: Option<u8>,
|
|
/// Individual window transparency ignore rules
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub transparency_ignore_rules: Option<Vec<MatchingRule>>,
|
|
/// Global default workspace padding (default: 10)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub default_workspace_padding: Option<i32>,
|
|
/// Global default container padding (default: 10)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub default_container_padding: Option<i32>,
|
|
/// Monitor and workspace configurations
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub monitors: Option<Vec<MonitorConfig>>,
|
|
/// Which Windows signal to use when hiding windows (default: Cloak)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub window_hiding_behaviour: Option<HidingBehaviour>,
|
|
/// Global work area (space used for tiling) offset (default: None)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub global_work_area_offset: Option<Rect>,
|
|
/// Individual window floating rules
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
#[serde(alias = "float_rules")]
|
|
pub ignore_rules: Option<Vec<MatchingRule>>,
|
|
/// Individual window force-manage rules
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub manage_rules: Option<Vec<MatchingRule>>,
|
|
/// Identify applications which should be managed as floating windows
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub floating_applications: Option<Vec<MatchingRule>>,
|
|
/// Identify border overflow applications
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub border_overflow_applications: Option<Vec<MatchingRule>>,
|
|
/// Identify tray and multi-window applications
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tray_and_multi_window_applications: Option<Vec<MatchingRule>>,
|
|
/// Identify applications that have the WS_EX_LAYERED extended window style
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub layered_applications: Option<Vec<MatchingRule>>,
|
|
/// Identify applications that send EVENT_OBJECT_NAMECHANGE on launch (very rare)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub object_name_change_applications: Option<Vec<MatchingRule>>,
|
|
/// Do not process EVENT_OBJECT_NAMECHANGE events as Show events for identified applications matching these title regexes
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub object_name_change_title_ignore_list: Option<Vec<String>>,
|
|
/// Set monitor index preferences
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub monitor_index_preferences: Option<HashMap<usize, Rect>>,
|
|
/// Set display index preferences
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub display_index_preferences: Option<HashMap<usize, String>>,
|
|
/// Stackbar configuration options
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub stackbar: Option<StackbarConfig>,
|
|
/// Animations configuration options
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub animation: Option<AnimationsConfig>,
|
|
/// Theme configuration options
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub theme: Option<KomorebiTheme>,
|
|
/// Identify applications which are slow to send initial event notifications
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub slow_application_identifiers: Option<Vec<MatchingRule>>,
|
|
/// How long to wait when compensating for slow applications, in milliseconds (default: 20)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub slow_application_compensation_time: Option<u64>,
|
|
/// Komorebi status bar configuration files for multiple instances on different monitors
|
|
// this option is a little special because it is only consumed by komorebic
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub bar_configurations: Option<Vec<PathBuf>>,
|
|
/// HEAVILY DISCOURAGED: Identify applications for which komorebi should forcibly remove title bars
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub remove_titlebar_applications: Option<Vec<MatchingRule>>,
|
|
/// Aspect ratio to resize with when toggling floating mode for a window
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub floating_window_aspect_ratio: Option<AspectRatio>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub struct AnimationsConfig {
|
|
/// Enable or disable animations (default: false)
|
|
pub enabled: PerAnimationPrefixConfig<bool>,
|
|
/// Set the animation duration in ms (default: 250)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub duration: Option<PerAnimationPrefixConfig<u64>>,
|
|
/// Set the animation style (default: Linear)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub style: Option<PerAnimationPrefixConfig<AnimationStyle>>,
|
|
/// Set the animation FPS (default: 60)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub fps: Option<u64>,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
#[serde(tag = "palette")]
|
|
pub enum KomorebiTheme {
|
|
/// A theme from catppuccin-egui
|
|
Catppuccin {
|
|
/// Name of the Catppuccin theme (theme previews: https://github.com/catppuccin/catppuccin)
|
|
name: komorebi_themes::Catppuccin,
|
|
/// Border colour when the container contains a single window (default: Blue)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
single_border: Option<komorebi_themes::CatppuccinValue>,
|
|
/// Border colour when the container contains multiple windows (default: Green)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
stack_border: Option<komorebi_themes::CatppuccinValue>,
|
|
/// Border colour when the container is in monocle mode (default: Pink)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
monocle_border: Option<komorebi_themes::CatppuccinValue>,
|
|
/// Border colour when the window is floating (default: Yellow)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
floating_border: Option<komorebi_themes::CatppuccinValue>,
|
|
/// Border colour when the container is unfocused (default: Base)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
unfocused_border: Option<komorebi_themes::CatppuccinValue>,
|
|
/// Stackbar focused tab text colour (default: Green)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
stackbar_focused_text: Option<komorebi_themes::CatppuccinValue>,
|
|
/// Stackbar unfocused tab text colour (default: Text)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
stackbar_unfocused_text: Option<komorebi_themes::CatppuccinValue>,
|
|
/// Stackbar tab background colour (default: Base)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
stackbar_background: Option<komorebi_themes::CatppuccinValue>,
|
|
/// Komorebi status bar accent (default: Blue)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
bar_accent: Option<komorebi_themes::CatppuccinValue>,
|
|
},
|
|
/// A theme from base16-egui-themes
|
|
Base16 {
|
|
/// Name of the Base16 theme (theme previews: https://tinted-theming.github.io/tinted-gallery/)
|
|
name: komorebi_themes::Base16,
|
|
/// Border colour when the container contains a single window (default: Base0D)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
single_border: Option<komorebi_themes::Base16Value>,
|
|
/// Border colour when the container contains multiple windows (default: Base0B)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
stack_border: Option<komorebi_themes::Base16Value>,
|
|
/// Border colour when the container is in monocle mode (default: Base0F)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
monocle_border: Option<komorebi_themes::Base16Value>,
|
|
/// Border colour when the window is floating (default: Base09)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
floating_border: Option<komorebi_themes::Base16Value>,
|
|
/// Border colour when the container is unfocused (default: Base01)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
unfocused_border: Option<komorebi_themes::Base16Value>,
|
|
/// Stackbar focused tab text colour (default: Base0B)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
stackbar_focused_text: Option<komorebi_themes::Base16Value>,
|
|
/// Stackbar unfocused tab text colour (default: Base05)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
stackbar_unfocused_text: Option<komorebi_themes::Base16Value>,
|
|
/// Stackbar tab background colour (default: Base01)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
stackbar_background: Option<komorebi_themes::Base16Value>,
|
|
/// Komorebi status bar accent (default: Base0D)
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
bar_accent: Option<komorebi_themes::Base16Value>,
|
|
},
|
|
}
|
|
|
|
impl StaticConfig {
|
|
pub fn end_of_life(raw: &str) {
|
|
let features = vec![
|
|
"focus_follows_mouse",
|
|
"custom_layout",
|
|
"custom_layout_rules",
|
|
];
|
|
|
|
let mut display = false;
|
|
|
|
for feature in features {
|
|
if raw.contains(feature) {
|
|
if !display {
|
|
display = true;
|
|
println!("\n\"{feature}\" is now end-of-life");
|
|
} else {
|
|
println!(r#""{feature}" is now end-of-life"#);
|
|
}
|
|
}
|
|
}
|
|
|
|
if display {
|
|
println!("\nEnd-of-life features will not receive any further bug fixes or updates; they should not be used\n")
|
|
}
|
|
}
|
|
|
|
pub fn aliases(raw: &str) {
|
|
let mut map = HashMap::new();
|
|
map.insert("border", ["active_window_border"]);
|
|
map.insert("border_width", ["active_window_border_width"]);
|
|
map.insert("border_offset", ["active_window_border_offset"]);
|
|
map.insert("border_colours", ["active_window_border_colours"]);
|
|
map.insert("border_style", ["active_window_border_style"]);
|
|
map.insert("applications.json", ["applications.yaml"]);
|
|
map.insert("ignore_rules", ["float_rules"]);
|
|
|
|
let mut display = false;
|
|
|
|
for aliases in map.values() {
|
|
for a in aliases {
|
|
if raw.contains(a) {
|
|
display = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if display {
|
|
println!("\nYour configuration file contains some options that have been renamed or deprecated:\n");
|
|
for (canonical, aliases) in map {
|
|
for alias in aliases {
|
|
if raw.contains(alias) {
|
|
println!(r#""{alias}" is now "{canonical}""#);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn deprecated(raw: &str) {
|
|
let deprecated_options = ["invisible_borders", "border_z_order"];
|
|
let deprecated_variants = vec![
|
|
("Hide", "window_hiding_behaviour", "Cloak"),
|
|
("Minimize", "window_hiding_behaviour", "Cloak"),
|
|
];
|
|
|
|
for option in deprecated_options {
|
|
if raw.contains(option) {
|
|
println!(r#""{option}" is deprecated and can be removed"#);
|
|
}
|
|
}
|
|
|
|
for (variant, option, recommended) in deprecated_variants {
|
|
if raw.contains(option) && raw.contains(variant) {
|
|
println!(
|
|
r#"The "{variant}" option for "{option}" is deprecated and can be removed or replaced with "{recommended}""#
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub struct TabsConfig {
|
|
/// Width of a stackbar tab
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub width: Option<i32>,
|
|
/// Focused tab text colour
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub focused_text: Option<Colour>,
|
|
/// Unfocused tab text colour
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub unfocused_text: Option<Colour>,
|
|
/// Tab background colour
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub background: Option<Colour>,
|
|
/// Font family
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub font_family: Option<String>,
|
|
/// Font size
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub font_size: Option<i32>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
|
pub struct StackbarConfig {
|
|
/// Stackbar height
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub height: Option<i32>,
|
|
/// Stackbar label
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub label: Option<StackbarLabel>,
|
|
/// Stackbar mode
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub mode: Option<StackbarMode>,
|
|
/// Stackbar tab configuration options
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tabs: Option<TabsConfig>,
|
|
}
|
|
|
|
impl From<&WindowManager> for StaticConfig {
|
|
#[allow(clippy::too_many_lines)]
|
|
fn from(value: &WindowManager) -> Self {
|
|
let mut monitors = vec![];
|
|
for m in value.monitors() {
|
|
monitors.push(MonitorConfig::from(m));
|
|
}
|
|
|
|
let border_colours = if border_manager::FOCUSED.load(Ordering::SeqCst) == 0 {
|
|
None
|
|
} else {
|
|
Option::from(BorderColours {
|
|
single: Option::from(Colour::from(border_manager::FOCUSED.load(Ordering::SeqCst))),
|
|
stack: Option::from(Colour::from(border_manager::STACK.load(Ordering::SeqCst))),
|
|
monocle: Option::from(Colour::from(border_manager::MONOCLE.load(Ordering::SeqCst))),
|
|
floating: Option::from(Colour::from(
|
|
border_manager::FLOATING.load(Ordering::SeqCst),
|
|
)),
|
|
unfocused: Option::from(Colour::from(
|
|
border_manager::UNFOCUSED.load(Ordering::SeqCst),
|
|
)),
|
|
})
|
|
};
|
|
|
|
Self {
|
|
invisible_borders: None,
|
|
resize_delta: Option::from(value.resize_delta),
|
|
window_container_behaviour: Option::from(
|
|
value.window_management_behaviour.current_behaviour,
|
|
),
|
|
float_override: Option::from(value.window_management_behaviour.float_override),
|
|
cross_monitor_move_behaviour: Option::from(value.cross_monitor_move_behaviour),
|
|
cross_boundary_behaviour: Option::from(value.cross_boundary_behaviour),
|
|
unmanaged_window_operation_behaviour: Option::from(
|
|
value.unmanaged_window_operation_behaviour,
|
|
),
|
|
minimum_window_height: Some(window::MINIMUM_HEIGHT.load(Ordering::SeqCst)),
|
|
minimum_window_width: Some(window::MINIMUM_WIDTH.load(Ordering::SeqCst)),
|
|
focus_follows_mouse: value.focus_follows_mouse,
|
|
mouse_follows_focus: Option::from(value.mouse_follows_focus),
|
|
app_specific_configuration_path: None,
|
|
border_width: Option::from(border_manager::BORDER_WIDTH.load(Ordering::SeqCst)),
|
|
border_offset: Option::from(border_manager::BORDER_OFFSET.load(Ordering::SeqCst)),
|
|
border: Option::from(border_manager::BORDER_ENABLED.load(Ordering::SeqCst)),
|
|
border_colours,
|
|
transparency: Option::from(
|
|
transparency_manager::TRANSPARENCY_ENABLED.load(Ordering::SeqCst),
|
|
),
|
|
transparency_alpha: Option::from(
|
|
transparency_manager::TRANSPARENCY_ALPHA.load(Ordering::SeqCst),
|
|
),
|
|
transparency_ignore_rules: None,
|
|
border_style: Option::from(STYLE.load()),
|
|
border_z_order: None,
|
|
border_implementation: Option::from(IMPLEMENTATION.load()),
|
|
default_workspace_padding: Option::from(
|
|
DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),
|
|
),
|
|
default_container_padding: Option::from(
|
|
DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst),
|
|
),
|
|
monitors: Option::from(monitors),
|
|
window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()),
|
|
global_work_area_offset: value.work_area_offset,
|
|
ignore_rules: None,
|
|
floating_applications: None,
|
|
manage_rules: None,
|
|
border_overflow_applications: None,
|
|
tray_and_multi_window_applications: None,
|
|
layered_applications: None,
|
|
object_name_change_applications: Option::from(
|
|
OBJECT_NAME_CHANGE_ON_LAUNCH.lock().clone(),
|
|
),
|
|
object_name_change_title_ignore_list: Option::from(
|
|
OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST
|
|
.lock()
|
|
.clone()
|
|
.iter()
|
|
.map(|r| r.to_string())
|
|
.collect::<Vec<_>>(),
|
|
),
|
|
monitor_index_preferences: Option::from(MONITOR_INDEX_PREFERENCES.lock().clone()),
|
|
display_index_preferences: Option::from(DISPLAY_INDEX_PREFERENCES.read().clone()),
|
|
stackbar: None,
|
|
animation: None,
|
|
theme: None,
|
|
slow_application_compensation_time: Option::from(
|
|
SLOW_APPLICATION_COMPENSATION_TIME.load(Ordering::SeqCst),
|
|
),
|
|
slow_application_identifiers: Option::from(SLOW_APPLICATION_IDENTIFIERS.lock().clone()),
|
|
bar_configurations: None,
|
|
remove_titlebar_applications: Option::from(NO_TITLEBAR.lock().clone()),
|
|
floating_window_aspect_ratio: Option::from(*FLOATING_WINDOW_TOGGLE_ASPECT_RATIO.lock()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl StaticConfig {
|
|
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
|
|
fn apply_globals(&mut self) -> Result<()> {
|
|
*FLOATING_WINDOW_TOGGLE_ASPECT_RATIO.lock() = self
|
|
.floating_window_aspect_ratio
|
|
.unwrap_or(AspectRatio::Predefined(PredefinedAspectRatio::Standard));
|
|
|
|
if let Some(monitor_index_preferences) = &self.monitor_index_preferences {
|
|
let mut preferences = MONITOR_INDEX_PREFERENCES.lock();
|
|
preferences.clone_from(monitor_index_preferences);
|
|
}
|
|
|
|
if let Some(display_index_preferences) = &self.display_index_preferences {
|
|
let mut preferences = DISPLAY_INDEX_PREFERENCES.write();
|
|
preferences.clone_from(display_index_preferences);
|
|
}
|
|
|
|
if let Some(behaviour) = self.window_hiding_behaviour {
|
|
let mut window_hiding_behaviour = HIDING_BEHAVIOUR.lock();
|
|
*window_hiding_behaviour = behaviour;
|
|
}
|
|
|
|
if let Some(height) = self.minimum_window_height {
|
|
window::MINIMUM_HEIGHT.store(height, Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(width) = self.minimum_window_width {
|
|
window::MINIMUM_WIDTH.store(width, Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(animations) = &self.animation {
|
|
match &animations.enabled {
|
|
PerAnimationPrefixConfig::Prefix(enabled) => {
|
|
ANIMATION_ENABLED_PER_ANIMATION.lock().clone_from(enabled);
|
|
}
|
|
PerAnimationPrefixConfig::Global(enabled) => {
|
|
ANIMATION_ENABLED_GLOBAL.store(*enabled, Ordering::SeqCst);
|
|
ANIMATION_ENABLED_PER_ANIMATION.lock().clear();
|
|
}
|
|
}
|
|
|
|
match &animations.style {
|
|
Some(PerAnimationPrefixConfig::Prefix(style)) => {
|
|
ANIMATION_STYLE_PER_ANIMATION.lock().clone_from(style);
|
|
}
|
|
Some(PerAnimationPrefixConfig::Global(style)) => {
|
|
let mut animation_style = ANIMATION_STYLE_GLOBAL.lock();
|
|
*animation_style = *style;
|
|
ANIMATION_STYLE_PER_ANIMATION.lock().clear();
|
|
}
|
|
None => {}
|
|
}
|
|
|
|
match &animations.duration {
|
|
Some(PerAnimationPrefixConfig::Prefix(duration)) => {
|
|
ANIMATION_DURATION_PER_ANIMATION.lock().clone_from(duration);
|
|
}
|
|
Some(PerAnimationPrefixConfig::Global(duration)) => {
|
|
ANIMATION_DURATION_GLOBAL.store(*duration, Ordering::SeqCst);
|
|
ANIMATION_DURATION_PER_ANIMATION.lock().clear();
|
|
}
|
|
None => {}
|
|
}
|
|
|
|
ANIMATION_FPS.store(
|
|
animations.fps.unwrap_or(DEFAULT_ANIMATION_FPS),
|
|
Ordering::SeqCst,
|
|
);
|
|
}
|
|
|
|
if let Some(container) = self.default_container_padding {
|
|
DEFAULT_CONTAINER_PADDING.store(container, Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(workspace) = self.default_workspace_padding {
|
|
DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst);
|
|
}
|
|
|
|
border_manager::BORDER_WIDTH.store(self.border_width.unwrap_or(8), Ordering::SeqCst);
|
|
border_manager::BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst);
|
|
|
|
if let Some(enabled) = &self.border {
|
|
border_manager::BORDER_ENABLED.store(*enabled, Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(colours) = &self.border_colours {
|
|
if let Some(single) = colours.single {
|
|
border_manager::FOCUSED.store(u32::from(single), Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(stack) = colours.stack {
|
|
border_manager::STACK.store(u32::from(stack), Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(monocle) = colours.monocle {
|
|
border_manager::MONOCLE.store(u32::from(monocle), Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(floating) = colours.floating {
|
|
border_manager::FLOATING.store(u32::from(floating), Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(unfocused) = colours.unfocused {
|
|
border_manager::UNFOCUSED.store(u32::from(unfocused), Ordering::SeqCst);
|
|
}
|
|
}
|
|
|
|
STYLE.store(self.border_style.unwrap_or_default());
|
|
|
|
if !*WINDOWS_11
|
|
&& matches!(
|
|
self.border_implementation.unwrap_or_default(),
|
|
BorderImplementation::Windows
|
|
)
|
|
{
|
|
tracing::error!(
|
|
"BorderImplementation::Windows is only supported on Windows 11 and above"
|
|
);
|
|
} else {
|
|
IMPLEMENTATION.store(self.border_implementation.unwrap_or_default());
|
|
match IMPLEMENTATION.load() {
|
|
BorderImplementation::Komorebi => {
|
|
border_manager::destroy_all_borders()?;
|
|
}
|
|
BorderImplementation::Windows => {
|
|
// TODO: figure out how to call wm.remove_all_accents here
|
|
}
|
|
}
|
|
|
|
border_manager::send_notification(None);
|
|
}
|
|
|
|
transparency_manager::TRANSPARENCY_ENABLED
|
|
.store(self.transparency.unwrap_or(false), Ordering::SeqCst);
|
|
transparency_manager::TRANSPARENCY_ALPHA
|
|
.store(self.transparency_alpha.unwrap_or(200), Ordering::SeqCst);
|
|
|
|
let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock();
|
|
let mut regex_identifiers = REGEX_IDENTIFIERS.lock();
|
|
let mut manage_identifiers = MANAGE_IDENTIFIERS.lock();
|
|
let mut tray_and_multi_window_identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock();
|
|
let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock();
|
|
let mut object_name_change_title_ignore_list = OBJECT_NAME_CHANGE_TITLE_IGNORE_LIST.lock();
|
|
let mut layered_identifiers = LAYERED_WHITELIST.lock();
|
|
let mut transparency_blacklist = TRANSPARENCY_BLACKLIST.lock();
|
|
let mut slow_application_identifiers = SLOW_APPLICATION_IDENTIFIERS.lock();
|
|
let mut floating_applications = FLOATING_APPLICATIONS.lock();
|
|
let mut no_titlebar_applications = NO_TITLEBAR.lock();
|
|
|
|
if let Some(rules) = &mut self.ignore_rules {
|
|
populate_rules(rules, &mut ignore_identifiers, &mut regex_identifiers)?;
|
|
}
|
|
|
|
if let Some(rules) = &mut self.floating_applications {
|
|
populate_rules(rules, &mut floating_applications, &mut regex_identifiers)?;
|
|
}
|
|
|
|
if let Some(rules) = &mut self.manage_rules {
|
|
populate_rules(rules, &mut manage_identifiers, &mut regex_identifiers)?;
|
|
}
|
|
|
|
if let Some(rules) = &mut self.object_name_change_applications {
|
|
populate_rules(
|
|
rules,
|
|
&mut object_name_change_identifiers,
|
|
&mut regex_identifiers,
|
|
)?;
|
|
}
|
|
|
|
if let Some(regexes) = &mut self.object_name_change_title_ignore_list {
|
|
let mut updated = vec![];
|
|
for r in regexes {
|
|
if let Ok(regex) = Regex::new(r) {
|
|
updated.push(regex);
|
|
}
|
|
}
|
|
|
|
*object_name_change_title_ignore_list = updated;
|
|
}
|
|
|
|
if let Some(rules) = &mut self.layered_applications {
|
|
populate_rules(rules, &mut layered_identifiers, &mut regex_identifiers)?;
|
|
}
|
|
|
|
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(rules) = &mut self.transparency_ignore_rules {
|
|
populate_rules(rules, &mut transparency_blacklist, &mut regex_identifiers)?;
|
|
}
|
|
|
|
if let Some(rules) = &mut self.slow_application_identifiers {
|
|
populate_rules(
|
|
rules,
|
|
&mut slow_application_identifiers,
|
|
&mut regex_identifiers,
|
|
)?;
|
|
}
|
|
|
|
if let Some(rules) = &mut self.remove_titlebar_applications {
|
|
populate_rules(rules, &mut no_titlebar_applications, &mut regex_identifiers)?;
|
|
}
|
|
|
|
if let Some(stackbar) = &self.stackbar {
|
|
if let Some(height) = &stackbar.height {
|
|
STACKBAR_TAB_HEIGHT.store(*height, Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(label) = &stackbar.label {
|
|
STACKBAR_LABEL.store(*label);
|
|
}
|
|
|
|
if let Some(mode) = &stackbar.mode {
|
|
STACKBAR_MODE.store(*mode);
|
|
}
|
|
|
|
#[allow(clippy::assigning_clones)]
|
|
if let Some(tabs) = &stackbar.tabs {
|
|
if let Some(background) = &tabs.background {
|
|
STACKBAR_TAB_BACKGROUND_COLOUR.store((*background).into(), Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(colour) = &tabs.focused_text {
|
|
STACKBAR_FOCUSED_TEXT_COLOUR.store((*colour).into(), Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(colour) = &tabs.unfocused_text {
|
|
STACKBAR_UNFOCUSED_TEXT_COLOUR.store((*colour).into(), Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(width) = &tabs.width {
|
|
STACKBAR_TAB_WIDTH.store(*width, Ordering::SeqCst);
|
|
}
|
|
|
|
STACKBAR_FONT_SIZE.store(tabs.font_size.unwrap_or(0), Ordering::SeqCst);
|
|
*STACKBAR_FONT_FAMILY.lock() = tabs.font_family.clone();
|
|
}
|
|
}
|
|
|
|
if let Some(theme) = &self.theme {
|
|
theme_manager::send_notification(*theme);
|
|
}
|
|
|
|
if let Some(path) = &self.app_specific_configuration_path {
|
|
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,
|
|
)?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn read_raw(raw: &str) -> Result<Self> {
|
|
Ok(serde_json::from_str(raw)?)
|
|
}
|
|
|
|
pub fn read(path: &PathBuf) -> Result<Self> {
|
|
let content = std::fs::read_to_string(path)?;
|
|
let mut value: Self = serde_json::from_str(&content)?;
|
|
|
|
if let Some(path) = &mut value.app_specific_configuration_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 {
|
|
for m in monitors {
|
|
for w in &mut m.workspaces {
|
|
if let Some(path) = &mut w.custom_layout {
|
|
*path = resolve_home_path(&*path)?;
|
|
}
|
|
|
|
if let Some(map) = &mut w.custom_layout_rules {
|
|
for path in map.values_mut() {
|
|
*path = resolve_home_path(&*path)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(bar_configurations) = &mut value.bar_configurations {
|
|
for path in bar_configurations {
|
|
*path = resolve_home_path(&*path)?;
|
|
}
|
|
}
|
|
|
|
Ok(value)
|
|
}
|
|
|
|
#[allow(clippy::too_many_lines)]
|
|
pub fn preload(
|
|
path: &PathBuf,
|
|
incoming: Receiver<WindowManagerEvent>,
|
|
unix_listener: Option<UnixListener>,
|
|
) -> Result<WindowManager> {
|
|
let mut value = Self::read(path)?;
|
|
value.apply_globals()?;
|
|
|
|
let listener = match unix_listener {
|
|
Some(listener) => listener,
|
|
None => {
|
|
let socket = DATA_DIR.join("komorebi.sock");
|
|
|
|
match std::fs::remove_file(&socket) {
|
|
Ok(()) => {}
|
|
Err(error) => match error.kind() {
|
|
// Doing this because ::exists() doesn't work reliably on Windows via IntelliJ
|
|
ErrorKind::NotFound => {}
|
|
_ => {
|
|
return Err(error.into());
|
|
}
|
|
},
|
|
};
|
|
|
|
UnixListener::bind(&socket)?
|
|
}
|
|
};
|
|
|
|
let mut wm = WindowManager {
|
|
monitors: Ring::default(),
|
|
monitor_usr_idx_map: HashMap::new(),
|
|
incoming_events: incoming,
|
|
command_listener: listener,
|
|
is_paused: false,
|
|
virtual_desktop_id: current_virtual_desktop(),
|
|
work_area_offset: value.global_work_area_offset,
|
|
window_management_behaviour: WindowManagementBehaviour {
|
|
current_behaviour: value
|
|
.window_container_behaviour
|
|
.unwrap_or(WindowContainerBehaviour::Create),
|
|
float_override: value.float_override.unwrap_or_default(),
|
|
},
|
|
cross_monitor_move_behaviour: value
|
|
.cross_monitor_move_behaviour
|
|
.unwrap_or(MoveBehaviour::Swap),
|
|
cross_boundary_behaviour: value
|
|
.cross_boundary_behaviour
|
|
.unwrap_or(CrossBoundaryBehaviour::Monitor),
|
|
unmanaged_window_operation_behaviour: value
|
|
.unmanaged_window_operation_behaviour
|
|
.unwrap_or(OperationBehaviour::Op),
|
|
resize_delta: value.resize_delta.unwrap_or(50),
|
|
focus_follows_mouse: value.focus_follows_mouse,
|
|
mouse_follows_focus: value.mouse_follows_focus.unwrap_or(true),
|
|
hotwatch: Hotwatch::new()?,
|
|
has_pending_raise_op: false,
|
|
pending_move_op: Arc::new(None),
|
|
already_moved_window_handles: Arc::new(Mutex::new(HashSet::new())),
|
|
uncloack_to_ignore: 0,
|
|
known_hwnds: HashMap::new(),
|
|
};
|
|
|
|
match value.focus_follows_mouse {
|
|
None => WindowsApi::disable_focus_follows_mouse()?,
|
|
Some(FocusFollowsMouseImplementation::Windows) => {
|
|
WindowsApi::enable_focus_follows_mouse()?;
|
|
}
|
|
Some(FocusFollowsMouseImplementation::Komorebi) => {}
|
|
};
|
|
|
|
let bytes = SocketMessage::ReloadStaticConfiguration(path.clone()).as_bytes()?;
|
|
|
|
wm.hotwatch.watch(path, move |event| match event.kind {
|
|
// Editing in Notepad sends a NoticeWrite while editing in (Neo)Vim sends
|
|
// a NoticeRemove, presumably because of the use of swap files?
|
|
EventKind::Modify(_) | EventKind::Remove(_) => {
|
|
let socket = DATA_DIR.join("komorebi.sock");
|
|
let mut stream =
|
|
UnixStream::connect(socket).expect("could not connect to komorebi.sock");
|
|
stream
|
|
.write_all(&bytes)
|
|
.expect("could not write to komorebi.sock");
|
|
}
|
|
_ => {}
|
|
})?;
|
|
|
|
Ok(wm)
|
|
}
|
|
|
|
pub fn postload(path: &PathBuf, wm: &Arc<Mutex<WindowManager>>) -> Result<()> {
|
|
let value = Self::read(path)?;
|
|
let mut wm = wm.lock();
|
|
|
|
let configs_with_preference: Vec<_> =
|
|
DISPLAY_INDEX_PREFERENCES.read().keys().copied().collect();
|
|
let mut configs_used = Vec::new();
|
|
|
|
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
|
|
workspace_matching_rules.clear();
|
|
drop(workspace_matching_rules);
|
|
|
|
let offset = wm.work_area_offset;
|
|
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
|
|
let preferred_config_idx = {
|
|
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
|
|
let c_idx = display_index_preferences.iter().find_map(|(c_idx, id)| {
|
|
(monitor
|
|
.serial_number_id()
|
|
.as_ref()
|
|
.is_some_and(|sn| sn == id)
|
|
|| monitor.device_id() == id)
|
|
.then_some(*c_idx)
|
|
});
|
|
c_idx
|
|
};
|
|
let idx = preferred_config_idx.or({
|
|
// Monitor without preferred config idx.
|
|
// Get index of first config that is not a preferred config of some other monitor
|
|
// and that has not been used yet. This might return `None` as well, in that case
|
|
// this monitor won't have a config tied to it and will use the default values.
|
|
let m_config_count = value
|
|
.monitors
|
|
.as_ref()
|
|
.map(|ms| ms.len())
|
|
.unwrap_or_default();
|
|
(0..m_config_count)
|
|
.find(|i| !configs_with_preference.contains(i) && !configs_used.contains(i))
|
|
});
|
|
if let Some(monitor_config) = value
|
|
.monitors
|
|
.as_ref()
|
|
.and_then(|ms| idx.and_then(|i| ms.get(i)))
|
|
{
|
|
if let Some(used_config_idx) = idx {
|
|
configs_used.push(used_config_idx);
|
|
}
|
|
|
|
monitor.ensure_workspace_count(monitor_config.workspaces.len());
|
|
monitor.set_work_area_offset(monitor_config.work_area_offset);
|
|
monitor.set_window_based_work_area_offset(
|
|
monitor_config.window_based_work_area_offset,
|
|
);
|
|
monitor.set_window_based_work_area_offset_limit(
|
|
monitor_config
|
|
.window_based_work_area_offset_limit
|
|
.unwrap_or(1),
|
|
);
|
|
monitor.set_container_padding(monitor_config.container_padding);
|
|
monitor.set_workspace_padding(monitor_config.workspace_padding);
|
|
|
|
monitor.update_workspaces_globals(offset);
|
|
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
|
|
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
|
|
ws.load_static_config(workspace_config)?;
|
|
}
|
|
}
|
|
|
|
// Check if this monitor config is the preferred config for this monitor and store
|
|
// a copy of the monitor itself on the monitor cache if it is.
|
|
if idx == preferred_config_idx {
|
|
let id = monitor
|
|
.serial_number_id()
|
|
.as_ref()
|
|
.map_or(monitor.device_id(), |sn| sn);
|
|
monitor_reconciliator::insert_in_monitor_cache(id, monitor.clone());
|
|
}
|
|
|
|
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
|
|
for (j, ws) in monitor_config.workspaces.iter().enumerate() {
|
|
if let Some(rules) = &ws.workspace_rules {
|
|
for r in rules {
|
|
workspace_matching_rules.push(WorkspaceMatchingRule {
|
|
monitor_index: i,
|
|
workspace_index: j,
|
|
matching_rule: r.clone(),
|
|
initial_only: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
if let Some(rules) = &ws.initial_workspace_rules {
|
|
for r in rules {
|
|
workspace_matching_rules.push(WorkspaceMatchingRule {
|
|
monitor_index: i,
|
|
workspace_index: j,
|
|
matching_rule: r.clone(),
|
|
initial_only: true,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for configs that should be tied to a specific display that isn't loaded right now
|
|
// and cache a monitor with those configs with the specific `serial_number_id` so that when
|
|
// those devices are connected later we can use the correct config from the cache.
|
|
if configs_with_preference.len() > configs_used.len() {
|
|
for i in configs_with_preference
|
|
.iter()
|
|
.filter(|i| !configs_used.contains(i))
|
|
{
|
|
let id = {
|
|
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
|
|
display_index_preferences.get(i).cloned()
|
|
};
|
|
if let (Some(id), Some(monitor_config)) =
|
|
(id, value.monitors.as_ref().and_then(|ms| ms.get(*i)))
|
|
{
|
|
// The name, device, device_id and serial_number_id can be empty here since
|
|
// once the monitor with this preferred index actually connects the
|
|
// `load_monitor_information` function will update these fields.
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"".into(),
|
|
"".into(),
|
|
"".into(),
|
|
None,
|
|
);
|
|
|
|
m.ensure_workspace_count(monitor_config.workspaces.len());
|
|
m.set_work_area_offset(monitor_config.work_area_offset);
|
|
m.set_window_based_work_area_offset(
|
|
monitor_config.window_based_work_area_offset,
|
|
);
|
|
m.set_window_based_work_area_offset_limit(
|
|
monitor_config
|
|
.window_based_work_area_offset_limit
|
|
.unwrap_or(1),
|
|
);
|
|
m.set_container_padding(monitor_config.container_padding);
|
|
m.set_workspace_padding(monitor_config.workspace_padding);
|
|
|
|
m.update_workspaces_globals(offset);
|
|
|
|
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
|
|
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
|
|
ws.load_static_config(workspace_config)?;
|
|
}
|
|
}
|
|
|
|
monitor_reconciliator::insert_in_monitor_cache(&id, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
wm.enforce_workspace_rules()?;
|
|
|
|
if value.border == Some(true) {
|
|
border_manager::BORDER_ENABLED.store(true, Ordering::SeqCst);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn reload(path: &PathBuf, wm: &mut WindowManager) -> Result<()> {
|
|
let mut value = Self::read(path)?;
|
|
|
|
value.apply_globals()?;
|
|
|
|
let configs_with_preference: Vec<_> =
|
|
DISPLAY_INDEX_PREFERENCES.read().keys().copied().collect();
|
|
let mut configs_used = Vec::new();
|
|
|
|
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
|
|
workspace_matching_rules.clear();
|
|
drop(workspace_matching_rules);
|
|
|
|
let offset = wm.work_area_offset;
|
|
for (i, monitor) in wm.monitors_mut().iter_mut().enumerate() {
|
|
let preferred_config_idx = {
|
|
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
|
|
let c_idx = display_index_preferences.iter().find_map(|(c_idx, id)| {
|
|
(monitor
|
|
.serial_number_id()
|
|
.as_ref()
|
|
.is_some_and(|sn| sn == id)
|
|
|| monitor.device_id() == id)
|
|
.then_some(*c_idx)
|
|
});
|
|
c_idx
|
|
};
|
|
let idx = preferred_config_idx.or({
|
|
// Monitor without preferred config idx.
|
|
// Get index of first config that is not a preferred config of some other monitor
|
|
// and that has not been used yet. This might return `None` as well, in that case
|
|
// this monitor won't have a config tied to it and will use the default values.
|
|
let m_config_count = value
|
|
.monitors
|
|
.as_ref()
|
|
.map(|ms| ms.len())
|
|
.unwrap_or_default();
|
|
(0..m_config_count)
|
|
.find(|i| !configs_with_preference.contains(i) && !configs_used.contains(i))
|
|
});
|
|
if let Some(monitor_config) = value
|
|
.monitors
|
|
.as_ref()
|
|
.and_then(|ms| idx.and_then(|i| ms.get(i)))
|
|
{
|
|
if let Some(used_config_idx) = idx {
|
|
configs_used.push(used_config_idx);
|
|
}
|
|
|
|
monitor.ensure_workspace_count(monitor_config.workspaces.len());
|
|
if monitor.work_area_offset().is_none() {
|
|
monitor.set_work_area_offset(monitor_config.work_area_offset);
|
|
}
|
|
monitor.set_window_based_work_area_offset(
|
|
monitor_config.window_based_work_area_offset,
|
|
);
|
|
monitor.set_window_based_work_area_offset_limit(
|
|
monitor_config
|
|
.window_based_work_area_offset_limit
|
|
.unwrap_or(1),
|
|
);
|
|
monitor.set_container_padding(monitor_config.container_padding);
|
|
monitor.set_workspace_padding(monitor_config.workspace_padding);
|
|
|
|
monitor.update_workspaces_globals(offset);
|
|
|
|
for (j, ws) in monitor.workspaces_mut().iter_mut().enumerate() {
|
|
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
|
|
ws.load_static_config(workspace_config)?;
|
|
}
|
|
}
|
|
|
|
// Check if this monitor config is the preferred config for this monitor and store
|
|
// a copy of the monitor itself on the monitor cache if it is.
|
|
if idx == preferred_config_idx {
|
|
let id = monitor
|
|
.serial_number_id()
|
|
.as_ref()
|
|
.map_or(monitor.device_id(), |sn| sn);
|
|
monitor_reconciliator::insert_in_monitor_cache(id, monitor.clone());
|
|
}
|
|
|
|
let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock();
|
|
for (j, ws) in monitor_config.workspaces.iter().enumerate() {
|
|
if let Some(rules) = &ws.workspace_rules {
|
|
for r in rules {
|
|
workspace_matching_rules.push(WorkspaceMatchingRule {
|
|
monitor_index: i,
|
|
workspace_index: j,
|
|
matching_rule: r.clone(),
|
|
initial_only: false,
|
|
});
|
|
}
|
|
}
|
|
|
|
if let Some(rules) = &ws.initial_workspace_rules {
|
|
for r in rules {
|
|
workspace_matching_rules.push(WorkspaceMatchingRule {
|
|
monitor_index: i,
|
|
workspace_index: j,
|
|
matching_rule: r.clone(),
|
|
initial_only: true,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for configs that should be tied to a specific display that isn't loaded right now
|
|
// and cache a monitor with those configs with the specific `serial_number_id` so that when
|
|
// those devices are connected later we can use the correct config from the cache.
|
|
if configs_with_preference.len() > configs_used.len() {
|
|
for i in configs_with_preference
|
|
.iter()
|
|
.filter(|i| !configs_used.contains(i))
|
|
{
|
|
let id = {
|
|
let display_index_preferences = DISPLAY_INDEX_PREFERENCES.read();
|
|
display_index_preferences.get(i).cloned()
|
|
};
|
|
if let (Some(id), Some(monitor_config)) =
|
|
(id, value.monitors.as_ref().and_then(|ms| ms.get(*i)))
|
|
{
|
|
// The name, device, device_id and serial_number_id can be empty here since
|
|
// once the monitor with this preferred index actually connects the
|
|
// `load_monitor_information` function will update these fields.
|
|
let mut m = monitor::new(
|
|
0,
|
|
Rect::default(),
|
|
Rect::default(),
|
|
"".into(),
|
|
"".into(),
|
|
"".into(),
|
|
None,
|
|
);
|
|
|
|
m.ensure_workspace_count(monitor_config.workspaces.len());
|
|
m.set_work_area_offset(monitor_config.work_area_offset);
|
|
m.set_window_based_work_area_offset(
|
|
monitor_config.window_based_work_area_offset,
|
|
);
|
|
m.set_window_based_work_area_offset_limit(
|
|
monitor_config
|
|
.window_based_work_area_offset_limit
|
|
.unwrap_or(1),
|
|
);
|
|
m.set_container_padding(monitor_config.container_padding);
|
|
m.set_workspace_padding(monitor_config.workspace_padding);
|
|
|
|
m.update_workspaces_globals(offset);
|
|
|
|
for (j, ws) in m.workspaces_mut().iter_mut().enumerate() {
|
|
if let Some(workspace_config) = monitor_config.workspaces.get(j) {
|
|
ws.load_static_config(workspace_config)?;
|
|
}
|
|
}
|
|
|
|
monitor_reconciliator::insert_in_monitor_cache(&id, m);
|
|
}
|
|
}
|
|
}
|
|
|
|
wm.enforce_workspace_rules()?;
|
|
|
|
if let Some(enabled) = value.border {
|
|
border_manager::BORDER_ENABLED.store(enabled, Ordering::SeqCst);
|
|
}
|
|
|
|
if let Some(val) = value.window_container_behaviour {
|
|
wm.window_management_behaviour.current_behaviour = val;
|
|
}
|
|
|
|
if let Some(val) = value.float_override {
|
|
wm.window_management_behaviour.float_override = val;
|
|
}
|
|
|
|
if let Some(val) = value.cross_monitor_move_behaviour {
|
|
wm.cross_monitor_move_behaviour = val;
|
|
}
|
|
|
|
if let Some(val) = value.cross_boundary_behaviour {
|
|
wm.cross_boundary_behaviour = val;
|
|
}
|
|
|
|
if let Some(val) = value.unmanaged_window_operation_behaviour {
|
|
wm.unmanaged_window_operation_behaviour = val;
|
|
}
|
|
|
|
if let Some(val) = value.resize_delta {
|
|
wm.resize_delta = val;
|
|
}
|
|
|
|
if let Some(val) = value.mouse_follows_focus {
|
|
wm.mouse_follows_focus = val;
|
|
}
|
|
|
|
wm.work_area_offset = value.global_work_area_offset;
|
|
|
|
match value.focus_follows_mouse {
|
|
None => WindowsApi::disable_focus_follows_mouse()?,
|
|
Some(FocusFollowsMouseImplementation::Windows) => {
|
|
WindowsApi::enable_focus_follows_mouse()?;
|
|
}
|
|
Some(FocusFollowsMouseImplementation::Komorebi) => {}
|
|
};
|
|
|
|
wm.focus_follows_mouse = value.focus_follows_mouse;
|
|
|
|
let monitor_count = wm.monitors().len();
|
|
|
|
for i in 0..monitor_count {
|
|
wm.update_focused_workspace_by_monitor_idx(i)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn populate_option(
|
|
entry: &mut ApplicationConfiguration,
|
|
identifiers: &mut Vec<MatchingRule>,
|
|
regex_identifiers: &mut HashMap<String, Regex>,
|
|
) -> 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<MatchingRule>,
|
|
identifiers: &mut Vec<MatchingRule>,
|
|
regex_identifiers: &mut HashMap<String, Regex>,
|
|
) -> 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(())
|
|
}
|
|
|
|
#[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)]
|
|
mod tests {
|
|
use crate::StaticConfig;
|
|
|
|
#[test]
|
|
fn backwards_compat() {
|
|
let root = vec!["0.1.17", "0.1.18", "0.1.19"];
|
|
let docs = vec![
|
|
"0.1.20", "0.1.21", "0.1.22", "0.1.23", "0.1.24", "0.1.25", "0.1.26", "0.1.27",
|
|
"0.1.28", "0.1.29", "0.1.30", "0.1.31", "0.1.32", "0.1.33", "0.1.34",
|
|
];
|
|
|
|
let mut versions = vec![];
|
|
|
|
let client = reqwest::blocking::Client::new();
|
|
|
|
for version in root {
|
|
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
|
|
versions.push((version, client.execute(request).unwrap().text().unwrap()));
|
|
}
|
|
|
|
for version in docs {
|
|
let request = client.get(format!("https://raw.githubusercontent.com/LGUG2Z/komorebi/refs/tags/v{version}/docs/komorebi.example.json")).header("User-Agent", "komorebi-backwards-compat-test").build().unwrap();
|
|
versions.push((version, client.execute(request).unwrap().text().unwrap()));
|
|
}
|
|
|
|
for (version, config) in versions {
|
|
println!("{version}");
|
|
StaticConfig::read_raw(&config).unwrap();
|
|
}
|
|
}
|
|
}
|