diff --git a/komorebi/src/core/config_generation.rs b/komorebi/src/core/config_generation.rs index 71ea8f9db..fe67134fc 100644 --- a/komorebi/src/core/config_generation.rs +++ b/komorebi/src/core/config_generation.rs @@ -59,6 +59,14 @@ pub enum MatchingRule { Composite(Vec), } +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct WorkspaceMatchingRule { + pub monitor_index: usize, + pub workspace_index: usize, + pub matching_rule: MatchingRule, + pub initial_only: bool, +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct IdWithIdentifier { pub kind: ApplicationIdentifier, diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index 2ada8ddcc..b80761028 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -62,6 +62,7 @@ pub use windows_api::*; use crate::core::config_generation::IdWithIdentifier; use crate::core::config_generation::MatchingRule; use crate::core::config_generation::MatchingStrategy; +use crate::core::config_generation::WorkspaceMatchingRule; use color_eyre::Result; use os_info::Version; use parking_lot::Mutex; @@ -74,8 +75,6 @@ use which::which; use winreg::enums::HKEY_CURRENT_USER; use winreg::RegKey; -type WorkspaceRule = (usize, usize, bool); - lazy_static! { static ref HIDDEN_HWNDS: Arc>> = Arc::new(Mutex::new(vec![])); static ref LAYERED_WHITELIST: Arc>> = Arc::new(Mutex::new(vec![ @@ -135,8 +134,8 @@ lazy_static! { Arc::new(Mutex::new(HashMap::new())); static ref DISPLAY_INDEX_PREFERENCES: Arc>> = Arc::new(Mutex::new(HashMap::new())); - static ref WORKSPACE_RULES: Arc>> = - Arc::new(Mutex::new(HashMap::new())); + static ref WORKSPACE_MATCHING_RULES: Arc>> = + Arc::new(Mutex::new(Vec::new())); static ref REGEX_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(HashMap::new())); static ref MANAGE_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![])); diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 2d137ea58..2169be974 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -44,6 +44,7 @@ use crate::border_manager; use crate::border_manager::IMPLEMENTATION; use crate::border_manager::STYLE; use crate::colour::Rgb; +use crate::config_generation::WorkspaceMatchingRule; use crate::current_virtual_desktop; use crate::notify_subscribers; use crate::stackbar_manager; @@ -81,7 +82,7 @@ use crate::SUBSCRIPTION_SOCKETS; use crate::TCP_CONNECTIONS; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; use crate::WINDOWS_11; -use crate::WORKSPACE_RULES; +use crate::WORKSPACE_MATCHING_RULES; use stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR; use stackbar_manager::STACKBAR_LABEL; use stackbar_manager::STACKBAR_MODE; @@ -269,58 +270,101 @@ impl WindowManager { self.set_workspace_padding(monitor_idx, workspace_idx, size)?; } } - SocketMessage::InitialWorkspaceRule(_, ref id, monitor_idx, workspace_idx) => { - self.handle_initial_workspace_rules(id, monitor_idx, workspace_idx)?; + SocketMessage::InitialWorkspaceRule(identifier, ref id, monitor_idx, workspace_idx) => { + let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock(); + let workspace_matching_rule = WorkspaceMatchingRule { + monitor_index: monitor_idx, + workspace_index: workspace_idx, + matching_rule: MatchingRule::Simple(IdWithIdentifier { + kind: identifier, + id: id.to_string(), + matching_strategy: Some(MatchingStrategy::Legacy), + }), + initial_only: true, + }; + + if !workspace_rules.contains(&workspace_matching_rule) { + workspace_rules.push(workspace_matching_rule); + } } - SocketMessage::InitialNamedWorkspaceRule(_, ref id, ref workspace) => { + SocketMessage::InitialNamedWorkspaceRule(identifier, ref id, ref workspace) => { if let Some((monitor_idx, workspace_idx)) = self.monitor_workspace_index_by_name(workspace) { - self.handle_initial_workspace_rules(id, monitor_idx, workspace_idx)?; + let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock(); + let workspace_matching_rule = WorkspaceMatchingRule { + monitor_index: monitor_idx, + workspace_index: workspace_idx, + matching_rule: MatchingRule::Simple(IdWithIdentifier { + kind: identifier, + id: id.to_string(), + matching_strategy: Some(MatchingStrategy::Legacy), + }), + initial_only: true, + }; + + if !workspace_rules.contains(&workspace_matching_rule) { + workspace_rules.push(workspace_matching_rule); + } } } - SocketMessage::WorkspaceRule(_, ref id, monitor_idx, workspace_idx) => { - self.handle_definitive_workspace_rules(id, monitor_idx, workspace_idx)?; + SocketMessage::WorkspaceRule(identifier, ref id, monitor_idx, workspace_idx) => { + let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock(); + let workspace_matching_rule = WorkspaceMatchingRule { + monitor_index: monitor_idx, + workspace_index: workspace_idx, + matching_rule: MatchingRule::Simple(IdWithIdentifier { + kind: identifier, + id: id.to_string(), + matching_strategy: Some(MatchingStrategy::Legacy), + }), + initial_only: false, + }; + + if !workspace_rules.contains(&workspace_matching_rule) { + workspace_rules.push(workspace_matching_rule); + } } - SocketMessage::NamedWorkspaceRule(_, ref id, ref workspace) => { + SocketMessage::NamedWorkspaceRule(identifier, ref id, ref workspace) => { if let Some((monitor_idx, workspace_idx)) = self.monitor_workspace_index_by_name(workspace) { - self.handle_definitive_workspace_rules(id, monitor_idx, workspace_idx)?; + let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock(); + let workspace_matching_rule = WorkspaceMatchingRule { + monitor_index: monitor_idx, + workspace_index: workspace_idx, + matching_rule: MatchingRule::Simple(IdWithIdentifier { + kind: identifier, + id: id.to_string(), + matching_strategy: Some(MatchingStrategy::Legacy), + }), + initial_only: false, + }; + + if !workspace_rules.contains(&workspace_matching_rule) { + workspace_rules.push(workspace_matching_rule); + } } } SocketMessage::ClearWorkspaceRules(monitor_idx, workspace_idx) => { - let mut workspace_rules = WORKSPACE_RULES.lock(); - let mut to_remove = vec![]; - for (id, (m_idx, w_idx, _)) in workspace_rules.iter() { - if monitor_idx == *m_idx && workspace_idx == *w_idx { - to_remove.push(id.clone()); - } - } + let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock(); - for rule in to_remove { - workspace_rules.remove(&rule); - } + workspace_rules.retain(|r| { + r.monitor_index != monitor_idx && r.workspace_index != workspace_idx + }); } SocketMessage::ClearNamedWorkspaceRules(ref workspace) => { if let Some((monitor_idx, workspace_idx)) = self.monitor_workspace_index_by_name(workspace) { - let mut workspace_rules = WORKSPACE_RULES.lock(); - let mut to_remove = vec![]; - for (id, (m_idx, w_idx, _)) in workspace_rules.iter() { - if monitor_idx == *m_idx && workspace_idx == *w_idx { - to_remove.push(id.clone()); - } - } - - for rule in to_remove { - workspace_rules.remove(&rule); - } + let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock(); + workspace_rules.retain(|r| { + r.monitor_index != monitor_idx && r.workspace_index != workspace_idx + }); } } SocketMessage::ClearAllWorkspaceRules => { - let mut workspace_rules = WORKSPACE_RULES.lock(); + let mut workspace_rules = WORKSPACE_MATCHING_RULES.lock(); workspace_rules.clear(); } SocketMessage::ManageRule(identifier, ref id) => { @@ -1102,7 +1146,7 @@ impl WindowManager { // Check that this is a valid static config file first if StaticConfig::read(config).is_ok() { // Clear workspace rules; these will need to be replaced - WORKSPACE_RULES.lock().clear(); + WORKSPACE_MATCHING_RULES.lock().clear(); // Pause so that restored windows come to the foreground from all workspaces self.is_paused = true; // Bring all windows to the foreground @@ -1496,51 +1540,6 @@ impl WindowManager { tracing::info!("processed"); Ok(()) } - - #[tracing::instrument(skip(self), level = "debug")] - fn handle_initial_workspace_rules( - &mut self, - id: &String, - monitor_idx: usize, - workspace_idx: usize, - ) -> Result<()> { - self.handle_workspace_rules(id, monitor_idx, workspace_idx, true)?; - - Ok(()) - } - - #[tracing::instrument(skip(self), level = "debug")] - fn handle_definitive_workspace_rules( - &mut self, - id: &String, - monitor_idx: usize, - workspace_idx: usize, - ) -> Result<()> { - self.handle_workspace_rules(id, monitor_idx, workspace_idx, false)?; - - Ok(()) - } - - #[tracing::instrument(skip(self), level = "debug")] - pub fn handle_workspace_rules( - &mut self, - id: &String, - monitor_idx: usize, - workspace_idx: usize, - initial_workspace_rule: bool, - ) -> Result<()> { - { - let mut workspace_rules = WORKSPACE_RULES.lock(); - workspace_rules.insert( - id.to_string(), - (monitor_idx, workspace_idx, initial_workspace_rule), - ); - } - - self.enforce_workspace_rules()?; - - Ok(()) - } } pub fn read_commands_uds(wm: &Arc>, mut stream: UnixStream) -> Result<()> { diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 56397f4fd..409025ed9 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -45,17 +45,16 @@ use crate::REGEX_IDENTIFIERS; use crate::TRANSPARENCY_BLACKLIST; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; use crate::WINDOWS_11; -use crate::WORKSPACE_RULES; +use crate::WORKSPACE_MATCHING_RULES; +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::IdWithIdentifier; 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::ApplicationIdentifier; use crate::core::BorderStyle; use crate::core::DefaultLayout; use crate::core::FocusFollowsMouseImplementation; @@ -121,10 +120,10 @@ pub struct WorkspaceConfig { pub workspace_padding: Option, /// Initial workspace application rules #[serde(skip_serializing_if = "Option::is_none")] - pub initial_workspace_rules: Option>, + pub initial_workspace_rules: Option>, /// Permanent workspace application rules #[serde(skip_serializing_if = "Option::is_none")] - pub workspace_rules: Option>, + pub workspace_rules: Option>, /// 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, @@ -142,37 +141,6 @@ impl From<&Workspace> for WorkspaceConfig { } } - let workspace_rules = WORKSPACE_RULES.lock(); - let mut initial_ws_rules = vec![]; - let mut ws_rules = vec![]; - - for (identifier, (_, _, is_initial)) in &*workspace_rules { - if identifier.ends_with("exe") { - let rule = IdWithIdentifier { - kind: ApplicationIdentifier::Exe, - id: identifier.clone(), - matching_strategy: None, - }; - - if *is_initial { - initial_ws_rules.push(rule); - } else { - ws_rules.push(rule); - } - } - } - - let initial_ws_rules = if initial_ws_rules.is_empty() { - None - } else { - Option::from(initial_ws_rules) - }; - let ws_rules = if ws_rules.is_empty() { - None - } else { - Option::from(ws_rules) - }; - let default_container_padding = DEFAULT_CONTAINER_PADDING.load(Ordering::SeqCst); let default_workspace_padding = DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst); @@ -208,8 +176,8 @@ impl From<&Workspace> for WorkspaceConfig { custom_layout_rules: None, container_padding, workspace_padding, - initial_workspace_rules: initial_ws_rules, - workspace_rules: ws_rules, + initial_workspace_rules: None, + workspace_rules: None, apply_window_based_work_area_offset: Some(value.apply_window_based_work_area_offset()), } } @@ -515,95 +483,6 @@ impl From<&WindowManager> for StaticConfig { monitors.push(MonitorConfig::from(m)); } - let mut to_remove = vec![]; - let mut to_add_initial = vec![]; - let mut to_add_persistent = vec![]; - - let workspace_rules = WORKSPACE_RULES.lock(); - for (m_idx, m) in monitors.iter().enumerate() { - for (w_idx, w) in m.workspaces.iter().enumerate() { - if let Some(rules) = &w.initial_workspace_rules { - for iwsr in rules { - for (identifier, (monitor_idx, workspace_idx, _)) in &*workspace_rules { - if iwsr.id.eq(identifier) - && (*monitor_idx != m_idx || *workspace_idx != w_idx) - { - to_remove.push((m_idx, w_idx, iwsr.id.clone())); - } - } - } - } - - for (identifier, (monitor_idx, workspace_idx, initial)) in &*workspace_rules { - if *initial && (*monitor_idx == m_idx && *workspace_idx == w_idx) { - to_add_initial.push((m_idx, w_idx, identifier.clone())); - } - } - - if let Some(rules) = &w.workspace_rules { - for wsr in rules { - for (identifier, (monitor_idx, workspace_idx, _)) in &*workspace_rules { - if wsr.id.eq(identifier) - && (*monitor_idx != m_idx || *workspace_idx != w_idx) - { - to_remove.push((m_idx, w_idx, wsr.id.clone())); - } - } - } - } - - for (identifier, (monitor_idx, workspace_idx, initial)) in &*workspace_rules { - if !*initial && (*monitor_idx == m_idx && *workspace_idx == w_idx) { - to_add_persistent.push((m_idx, w_idx, identifier.clone())); - } - } - } - } - - for (m_idx, w_idx, id) in to_remove { - if let Some(monitor) = monitors.get_mut(m_idx) { - if let Some(workspace) = monitor.workspaces.get_mut(w_idx) { - if workspace.workspace_rules.is_none() { - workspace.workspace_rules = Some(vec![]); - } - - if let Some(rules) = &mut workspace.workspace_rules { - rules.retain(|r| r.id != id); - for (monitor_idx, workspace_idx, id) in &to_add_persistent { - if m_idx == *monitor_idx && w_idx == *workspace_idx { - rules.push(IdWithIdentifier { - kind: ApplicationIdentifier::Exe, - id: id.clone(), - matching_strategy: None, - }) - } - } - - rules.dedup(); - } - - if workspace.initial_workspace_rules.is_none() { - workspace.workspace_rules = Some(vec![]); - } - - if let Some(rules) = &mut workspace.initial_workspace_rules { - rules.retain(|r| r.id != id); - for (monitor_idx, workspace_idx, id) in &to_add_initial { - if m_idx == *monitor_idx && w_idx == *workspace_idx { - rules.push(IdWithIdentifier { - kind: ApplicationIdentifier::Exe, - id: id.clone(), - matching_strategy: None, - }) - } - } - - rules.dedup(); - } - } - } - } - let border_colours = if border_manager::FOCUSED.load(Ordering::SeqCst) == 0 { None } else { @@ -1150,22 +1029,35 @@ impl StaticConfig { } } + let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock(); for (j, ws) in monitor.workspaces.iter().enumerate() { if let Some(rules) = &ws.workspace_rules { for r in rules { - wm.handle_workspace_rules(&r.id, i, j, false)?; + 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 { - wm.handle_workspace_rules(&r.id, i, j, true)?; + workspace_matching_rules.push(WorkspaceMatchingRule { + monitor_index: i, + workspace_index: j, + matching_rule: r.clone(), + initial_only: true, + }); } } } } } + wm.enforce_workspace_rules()?; + if value.border == Some(true) { border_manager::BORDER_ENABLED.store(true, Ordering::SeqCst); } @@ -1201,22 +1093,36 @@ impl StaticConfig { } } + let mut workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock(); + workspace_matching_rules.clear(); for (j, ws) in monitor.workspaces.iter().enumerate() { if let Some(rules) = &ws.workspace_rules { for r in rules { - wm.handle_workspace_rules(&r.id, i, j, false)?; + 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 { - wm.handle_workspace_rules(&r.id, i, j, true)?; + workspace_matching_rules.push(WorkspaceMatchingRule { + monitor_index: i, + workspace_index: j, + matching_rule: r.clone(), + initial_only: true, + }); } } } } } + wm.enforce_workspace_rules()?; + if let Some(enabled) = value.border { border_manager::BORDER_ENABLED.store(enabled, Ordering::SeqCst); } diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index c9d6b3bab..9634c5167 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -16,7 +16,6 @@ use hotwatch::notify::ErrorKind as NotifyErrorKind; use hotwatch::EventKind; use hotwatch::Hotwatch; use parking_lot::Mutex; -use regex::Regex; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; @@ -43,12 +42,14 @@ use crate::core::WindowContainerBehaviour; use crate::border_manager; use crate::border_manager::STYLE; +use crate::config_generation::WorkspaceMatchingRule; use crate::container::Container; use crate::core::StackbarMode; use crate::current_virtual_desktop; use crate::load_configuration; use crate::monitor::Monitor; use crate::ring::Ring; +use crate::should_act_individual; use crate::stackbar_manager::STACKBAR_FOCUSED_TEXT_COLOUR; use crate::stackbar_manager::STACKBAR_LABEL; use crate::stackbar_manager::STACKBAR_MODE; @@ -67,7 +68,6 @@ use crate::BorderColours; use crate::Colour; use crate::CrossBoundaryBehaviour; use crate::Rgb; -use crate::WorkspaceRule; use crate::CUSTOM_FFM; use crate::DATA_DIR; use crate::DISPLAY_INDEX_PREFERENCES; @@ -79,9 +79,10 @@ use crate::MANAGE_IDENTIFIERS; use crate::MONITOR_INDEX_PREFERENCES; use crate::NO_TITLEBAR; use crate::OBJECT_NAME_CHANGE_ON_LAUNCH; +use crate::REGEX_IDENTIFIERS; use crate::REMOVE_TITLEBARS; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; -use crate::WORKSPACE_RULES; +use crate::WORKSPACE_MATCHING_RULES; #[derive(Debug)] pub struct WindowManager { @@ -142,7 +143,7 @@ pub struct GlobalState { pub name_change_on_launch_identifiers: Vec, pub monitor_index_preferences: HashMap, pub display_index_preferences: HashMap, - pub workspace_rules: HashMap, + pub workspace_rules: Vec, pub window_hiding_behaviour: HidingBehaviour, pub configuration_dir: PathBuf, pub data_dir: PathBuf, @@ -191,7 +192,7 @@ impl Default for GlobalState { name_change_on_launch_identifiers: OBJECT_NAME_CHANGE_ON_LAUNCH.lock().clone(), monitor_index_preferences: MONITOR_INDEX_PREFERENCES.lock().clone(), display_index_preferences: DISPLAY_INDEX_PREFERENCES.lock().clone(), - workspace_rules: WORKSPACE_RULES.lock().clone(), + workspace_rules: WORKSPACE_MATCHING_RULES.lock().clone(), window_hiding_behaviour: *HIDING_BEHAVIOUR.lock(), configuration_dir: HOME_DIR.clone(), data_dir: DATA_DIR.clone(), @@ -233,7 +234,6 @@ struct EnforceWorkspaceRuleOp { target_monitor_idx: usize, target_workspace_idx: usize, } - impl EnforceWorkspaceRuleOp { const fn is_origin(&self, monitor_idx: usize, workspace_idx: usize) -> bool { self.origin_monitor_idx == monitor_idx && self.origin_workspace_idx == workspace_idx @@ -450,7 +450,8 @@ impl WindowManager { .ok_or_else(|| anyhow!("there is no monitor with that index"))? .focused_workspace_idx(); - let workspace_rules = WORKSPACE_RULES.lock(); + let workspace_matching_rules = WORKSPACE_MATCHING_RULES.lock(); + let regex_identifiers = REGEX_IDENTIFIERS.lock(); // Go through all the monitors and workspaces for (i, monitor) in self.monitors().iter().enumerate() { for (j, workspace) in monitor.workspaces().iter().enumerate() { @@ -460,63 +461,61 @@ impl WindowManager { let exe_name = window.exe()?; let title = window.title()?; let class = window.class()?; - - let mut found_workspace_rule = workspace_rules.get(&exe_name); - - if found_workspace_rule.is_none() { - found_workspace_rule = workspace_rules.get(&title); - } - - if found_workspace_rule.is_none() { - found_workspace_rule = workspace_rules.get(&class); - } - - if found_workspace_rule.is_none() { - for (k, v) in workspace_rules.iter() { - if let Ok(re) = Regex::new(k) { - if re.is_match(&exe_name) { - found_workspace_rule = Some(v); + let path = window.path()?; + + for rule in &*workspace_matching_rules { + let matched = match &rule.matching_rule { + MatchingRule::Simple(r) => should_act_individual( + &title, + &exe_name, + &class, + &path, + r, + ®ex_identifiers, + ), + MatchingRule::Composite(r) => { + let mut composite_results = vec![]; + for identifier in r { + composite_results.push(should_act_individual( + &title, + &exe_name, + &class, + &path, + identifier, + ®ex_identifiers, + )); } - if re.is_match(&title) { - found_workspace_rule = Some(v); - } - - if re.is_match(&class) { - found_workspace_rule = Some(v); - } + composite_results.iter().all(|&x| x) } - } - } - - // If the executable names or titles of any of those windows are in our rules map - if let Some((monitor_idx, workspace_idx, apply_on_first_show_only)) = - found_workspace_rule - { - if *apply_on_first_show_only { - if !already_moved_window_handles.contains(&window.hwnd) { - already_moved_window_handles.insert(window.hwnd); + }; + if matched { + if rule.initial_only { + if !already_moved_window_handles.contains(&window.hwnd) { + already_moved_window_handles.insert(window.hwnd); + + self.add_window_handle_to_move_based_on_workspace_rule( + &window.title()?, + window.hwnd, + i, + j, + rule.monitor_index, + rule.workspace_index, + &mut to_move, + ); + } + } else { self.add_window_handle_to_move_based_on_workspace_rule( &window.title()?, window.hwnd, i, j, - *monitor_idx, - *workspace_idx, + rule.monitor_index, + rule.workspace_index, &mut to_move, ); } - } else { - self.add_window_handle_to_move_based_on_workspace_rule( - &window.title()?, - window.hwnd, - i, - j, - *monitor_idx, - *workspace_idx, - &mut to_move, - ); } } }