diff --git a/FancyWM/Controls/TilingWindow.xaml b/FancyWM/Controls/TilingWindow.xaml index 027305f..05dc8c2 100644 --- a/FancyWM/Controls/TilingWindow.xaml +++ b/FancyWM/Controls/TilingWindow.xaml @@ -29,6 +29,14 @@ + + + + + + + + diff --git a/FancyWM/MainWindow.xaml.cs b/FancyWM/MainWindow.xaml.cs index b77a746..b9d751f 100644 --- a/FancyWM/MainWindow.xaml.cs +++ b/FancyWM/MainWindow.xaml.cs @@ -192,15 +192,17 @@ await Dispatcher.InvokeAsync(() => .Select(_ => Unit.Default); var exclusionListSettings = settings - .DistinctUntilChanged(x => (x.ProcessIgnoreList, x.ClassIgnoreList)) + .DistinctUntilChanged(x => (x.ProcessIgnoreList, x.ClassIgnoreList, x.TitleIgnoreList)) .Do(async x => await Dispatcher.InvokeAsync(() => { var processMatchers = x.ProcessIgnoreList.Select(x => new ByProcessNameMatcher(x)); var classMatchers = x.ClassIgnoreList.Select(x => new ByClassNameMatcher(x)); + var titleMatchers = x.TitleIgnoreList.Select(x => new ByTitleMatcher(x)); m_tiling!.ExclusionMatchers = m_tiling.ExclusionMatchers - .Where(m => m is not ByProcessNameMatcher && m is not ByClassNameMatcher) + .Where(m => m is not ByProcessNameMatcher && m is not ByClassNameMatcher && m is not ByTitleMatcher) .Concat(processMatchers) .Concat(classMatchers) + .Concat(titleMatchers) .ToArray(); })) .Select(_ => Unit.Default); diff --git a/FancyWM/Models/Settings.cs b/FancyWM/Models/Settings.cs index 1763622..b468947 100644 --- a/FancyWM/Models/Settings.cs +++ b/FancyWM/Models/Settings.cs @@ -87,6 +87,8 @@ public Settings() "RAIL_WINDOW", ]; + public List TitleIgnoreList { get; init; } = []; + public bool RemindToRateReview { get; init; } = true; public bool ShowContextHints { get; init; } = true; diff --git a/FancyWM/Pages/Settings/RulesPage.xaml b/FancyWM/Pages/Settings/RulesPage.xaml index 87c2518..537386a 100644 --- a/FancyWM/Pages/Settings/RulesPage.xaml +++ b/FancyWM/Pages/Settings/RulesPage.xaml @@ -33,6 +33,13 @@ + + + + + + + diff --git a/FancyWM/Resources/Strings.Designer.cs b/FancyWM/Resources/Strings.Designer.cs index ac9e11c..85ed7ea 100644 --- a/FancyWM/Resources/Strings.Designer.cs +++ b/FancyWM/Resources/Strings.Designer.cs @@ -2331,7 +2331,16 @@ public static string Overlay_Window_AddRuleForProcess { return ResourceManager.GetString("Overlay.Window.AddRuleForProcess", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Add floating rule for window title. + /// + public static string Overlay_Window_AddRuleForTitle { + get { + return ResourceManager.GetString("Overlay.Window.AddRuleForTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Drag over another window to group. /// @@ -2439,7 +2448,25 @@ public static string Rules_ProcessIgnoreList_Description { return ResourceManager.GetString("Rules.ProcessIgnoreList.Description", resourceCulture); } } - + + /// + /// Looks up a localized string similar to Automatically float by window title. + /// + public static string Rules_TitleIgnoreList { + get { + return ResourceManager.GetString("Rules.TitleIgnoreList", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Floating mode will be automatically enabled for windows with a matching title. The matching is not case-sensitive and you may use regular expressions.. + /// + public static string Rules_TitleIgnoreList_Description { + get { + return ResourceManager.GetString("Rules.TitleIgnoreList.Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to Settings. /// diff --git a/FancyWM/Resources/Strings.resx b/FancyWM/Resources/Strings.resx index ba122c3..a32ab3c 100644 --- a/FancyWM/Resources/Strings.resx +++ b/FancyWM/Resources/Strings.resx @@ -540,6 +540,12 @@ Floating mode will be automatically enabled for windows belonging to one of the processes. Enter one process name per line, excluding the ".exe" extension (e.g. "explorer"). The matching is not case-sensitive and you may use regular expressions. + + Automatically float by window title + + + Floating mode will be automatically enabled for windows with a matching title. The matching is not case-sensitive and you may use regular expressions. + Settings @@ -684,6 +690,9 @@ Add floating rule for process + + Add floating rule for window title + More options diff --git a/FancyWM/TilingOverlayRenderer.cs b/FancyWM/TilingOverlayRenderer.cs index 78fcb00..12b0688 100644 --- a/FancyWM/TilingOverlayRenderer.cs +++ b/FancyWM/TilingOverlayRenderer.cs @@ -30,6 +30,7 @@ public class TilingOverlayRenderer : IDisposable public event EventHandler? FloatRequested; public event EventHandler? IgnoreProcessRequested; public event EventHandler? IgnoreClassRequested; + public event EventHandler? IgnoreTitleRequested; public event EventHandler? BeginHorizontalWithRequested; public event EventHandler? BeginVerticalWithRequested; public event EventHandler? BeginStackWithRequested; @@ -249,6 +250,7 @@ private void UpdateViewModels(IReadOnlyCollection snapshot, IReadOnl windowViewModel.StackActionPressed += WindowViewModel_StackActionPressed; windowViewModel.IgnoreClassPressed += WindowViewModel_IgnoreClassPressed; windowViewModel.IgnoreProcessPressed += WindowViewModel_IgnoreProcessPressed; + windowViewModel.IgnoreTitlePressed += WindowViewModel_IgnoreTitlePressed; UpdateViewModel(windowViewModel, windowNode, focusedPath); m_nodeViewModels.Add(node, windowViewModel); return windowViewModel; @@ -317,6 +319,11 @@ private void WindowViewModel_IgnoreClassPressed(object sender, RoutedEventArgs e IgnoreClassRequested?.Invoke(this, ((WindowNode)((TilingWindowViewModel)sender!).Node!)); } + private void WindowViewModel_IgnoreTitlePressed(object sender, RoutedEventArgs e) + { + IgnoreTitleRequested?.Invoke(this, ((WindowNode)((TilingWindowViewModel)sender!).Node!)); + } + private void OnSetPreviewWindows(IReadOnlySet oldValue, IReadOnlySet newValue) { foreach (var oldVm in m_nodeViewModels.Where(x => x.Key is WindowNode window && oldValue.Contains(window.WindowReference)) @@ -515,6 +522,7 @@ public void Dispose() FloatRequested = null; IgnoreProcessRequested = null; IgnoreClassRequested = null; + IgnoreTitleRequested = null; } } } diff --git a/FancyWM/TilingService.Private.cs b/FancyWM/TilingService.Private.cs index f15c05d..0e1c364 100644 --- a/FancyWM/TilingService.Private.cs +++ b/FancyWM/TilingService.Private.cs @@ -831,6 +831,13 @@ private void OnWindowIgnoreClassRequested(object? sender, WindowNode e) return x with { ClassIgnoreList = [.. x.ClassIgnoreList, ((WinMan.Windows.Win32Window)e.WindowReference).ClassName] }; }); } + private void OnWindowIgnoreTitleRequested(object? sender, WindowNode e) + { + App.Current.AppState.Settings.SaveAsync(x => + { + return x with { TitleIgnoreList = [.. x.TitleIgnoreList, e.WindowReference.Title] }; + }); + } private void OnTilingPanelMoving(object? sender, PanelNode panel) { diff --git a/FancyWM/TilingService.cs b/FancyWM/TilingService.cs index 88d815c..ee00d89 100644 --- a/FancyWM/TilingService.cs +++ b/FancyWM/TilingService.cs @@ -193,6 +193,7 @@ public TilingService(IWorkspace workspace, IDisplay display, IAnimationThread an m_gui.StackRequested += OnWindowStackRequested; m_gui.IgnoreProcessRequested += OnWindowIgnoreProcessRequested; m_gui.IgnoreClassRequested += OnWindowIgnoreClassRequested; + m_gui.IgnoreTitleRequested += OnWindowIgnoreTitleRequested; AutoRegisterWindows = autoRegisterWindows; diff --git a/FancyWM/Utilities/IWindowMatcher.cs b/FancyWM/Utilities/IWindowMatcher.cs index a3a8890..911f696 100644 --- a/FancyWM/Utilities/IWindowMatcher.cs +++ b/FancyWM/Utilities/IWindowMatcher.cs @@ -54,4 +54,14 @@ public bool Matches(IWindow window) return (window is WinMan.Windows.Win32Window w) && MatchHelpers.IsMatch(w.ClassName, ClassName); } } + + internal class ByTitleMatcher(string titlePattern) : IWindowMatcher + { + public string TitlePattern { get; } = titlePattern; + + public bool Matches(IWindow window) + { + return MatchHelpers.IsMatch(window.Title, TitlePattern); + } + } } diff --git a/FancyWM/ViewModels/SettingsViewModel.cs b/FancyWM/ViewModels/SettingsViewModel.cs index f871051..651ec8b 100644 --- a/FancyWM/ViewModels/SettingsViewModel.cs +++ b/FancyWM/ViewModels/SettingsViewModel.cs @@ -190,6 +190,8 @@ public ObservableCollection? Keybindings public IList? ClassIgnoreList { get => m_classIgnoreList; set => SetField(ref m_classIgnoreList, value); } + public IList? TitleIgnoreList { get => m_titleIgnoreList; set => SetField(ref m_titleIgnoreList, value); } + public bool MultiMonitorSupport { get => m_multiMonitorSupport; set => SetField(ref m_multiMonitorSupport, value); } public bool SoundOnFailure { get => m_soundOnFailure; set => SetField(ref m_soundOnFailure, value); } @@ -218,6 +220,7 @@ public ObservableCollection? Keybindings private bool m_activateOnCapsLock; private IList? m_processIgnoreList; private IList? m_classIgnoreList; + private IList? m_titleIgnoreList; private bool m_multiMonitorSupport; private bool m_showContextHints; private bool m_soundOnFailure; @@ -255,6 +258,7 @@ public SettingsViewModel(IObservableFileEntity observable) PanelFontSize = settings.PanelFontSize; ProcessIgnoreList = settings.ProcessIgnoreList; ClassIgnoreList = settings.ClassIgnoreList; + TitleIgnoreList = settings.TitleIgnoreList; MultiMonitorSupport = settings.MultiMonitorSupport; ShowContextHints = settings.ShowContextHints; SoundOnFailure = settings.SoundOnFailure; @@ -382,6 +386,7 @@ private void SaveChanges() ShowContextHints = ShowContextHints, ProcessIgnoreList = [.. ProcessIgnoreList!], ClassIgnoreList = [.. ClassIgnoreList!], + TitleIgnoreList = [.. TitleIgnoreList!], MultiMonitorSupport = MultiMonitorSupport, SoundOnFailure = SoundOnFailure, ShowFocus = ShowFocus, diff --git a/FancyWM/ViewModels/TilingWindowViewModel.cs b/FancyWM/ViewModels/TilingWindowViewModel.cs index c9732dc..d56b31b 100644 --- a/FancyWM/ViewModels/TilingWindowViewModel.cs +++ b/FancyWM/ViewModels/TilingWindowViewModel.cs @@ -59,6 +59,7 @@ private enum RevealState public event RoutedEventHandler? FloatActionPressed; public event RoutedEventHandler? IgnoreProcessPressed; public event RoutedEventHandler? IgnoreClassPressed; + public event RoutedEventHandler? IgnoreTitlePressed; public ICommand BeginHorizontalSplitWithCommand { get; } public ICommand BeginVerticalSplitWithCommand { get; } @@ -67,6 +68,7 @@ private enum RevealState public ICommand FloatCommand { get; } public ICommand IgnoreProcessCommand { get; } public ICommand IgnoreClassCommand { get; } + public ICommand IgnoreTitleCommand { get; } public TilingWindowViewModel() { @@ -77,6 +79,7 @@ public TilingWindowViewModel() FloatCommand = new DelegateCommand(_ => FloatActionPressed?.Invoke(this, new RoutedEventArgs())); IgnoreProcessCommand = new DelegateCommand(_ => IgnoreProcessPressed?.Invoke(this, new RoutedEventArgs())); IgnoreClassCommand = new DelegateCommand(_ => IgnoreClassPressed?.Invoke(this, new RoutedEventArgs())); + IgnoreTitleCommand = new DelegateCommand(_ => IgnoreTitlePressed?.Invoke(this, new RoutedEventArgs())); } public override void Dispose() @@ -85,6 +88,7 @@ public override void Dispose() FloatActionPressed = null; IgnoreProcessPressed = null; IgnoreClassPressed = null; + IgnoreTitlePressed = null; Node = null; }