diff --git a/docs/docs/getting-started/troubleshooting.md b/docs/docs/getting-started/troubleshooting.md index a4587c2b8..653d72353 100644 --- a/docs/docs/getting-started/troubleshooting.md +++ b/docs/docs/getting-started/troubleshooting.md @@ -2,13 +2,9 @@ ## Managing troublesome windows -Some windows like to remember their size and position. This can be a problem for Whim, because it will try to manage the window's size and position, and the window will fight back. +Some applications are difficult to manage. Whim will try to manage them as best as it can, but there are some limitations. For example, Firefox will try to remember its size and position, and will fight against Whim's attempts to manage it on first load. -The exposes an called . `LocationRestoringFilterManager` listens to events for these windows and will force their parent to do a layout two seconds after their first `WindowMoved` event, attempting to restore the window to its correct position. - -If this doesn't work, dragging a window's edge will force a layout, which should fix the window's position. This is an area which could use further improvement. - -Examples of troublesome windows include Firefox and JetBrains Gateway. +To get around this, Whim has s. These are used to tell Whim to ignore specific window messages (see [Event Constants](https://learn.microsoft.com/en-us/windows/win32/winauto/event-constants)). For example, the ignores all events until the first [`EVENT_OBJECT_CLOAKED`](https://learn.microsoft.com/en-us/windows/win32/winauto/event-constants#:~:text=EVENT_OBJECT_CLOAKED) event is received. ## Window launch locations @@ -18,7 +14,7 @@ To counteract this, the has a adds a border around the current window which Whim has tracked as having focus. Various options can be configured using the . > [!WARNING] -> The focus may remain on a window despite the focus shifting to an untracked window (e.g., the Start Menu). This depends on the config option . +> The focus may remain on a window despite the focus shifting to an untracked window (e.g., the Start Menu). This depends on the config option . ![Focus indicator demo](../../images/focus-indicator-demo.gif) diff --git a/docs/docs/toc.yml b/docs/docs/toc.yml index 4f6441bf2..5179f8943 100644 --- a/docs/docs/toc.yml +++ b/docs/docs/toc.yml @@ -5,6 +5,7 @@ items: - href: getting-started/customize.md - href: getting-started/plugins.md - href: getting-started/inspiration.md + - href: getting-started/troubleshooting.md - name: Customize - href: customize/scripting.md diff --git a/src/Whim.Tests/Store/WindowSector/Processors/FirefoxWindowProcessorTests.cs b/src/Whim.Tests/Store/WindowSector/Processors/FirefoxWindowProcessorTests.cs new file mode 100644 index 000000000..49de5bc67 --- /dev/null +++ b/src/Whim.Tests/Store/WindowSector/Processors/FirefoxWindowProcessorTests.cs @@ -0,0 +1,112 @@ +using Windows.Win32; + +namespace Whim.Tests; + +public class FirefoxWindowProcessorTests +{ + [Theory, AutoSubstituteData] + public void Create_Failure(IContext ctx, IWindow window) + { + // Given a window which is not a Firefox window + window.ProcessFileName.Returns("chrome.exe"); + + // When `Create` is called + IWindowProcessor? processor = FirefoxWindowProcessor.Create(ctx, window); + + // Then the processor should be null + Assert.Null(processor); + } + + [Theory, AutoSubstituteData] + public void Create_Success(IContext ctx, IWindow window) + { + // Given a window which is a Firefox window + window.ProcessFileName.Returns("firefox.exe"); + + // When `Create` is called + IWindowProcessor? processor = FirefoxWindowProcessor.Create(ctx, window); + + // Then the processor should not be null + Assert.NotNull(processor); + } + + [Theory] + [InlineAutoSubstituteData(PInvoke.EVENT_OBJECT_SHOW)] + [InlineAutoSubstituteData(PInvoke.EVENT_SYSTEM_FOREGROUND)] + [InlineAutoSubstituteData(PInvoke.EVENT_OBJECT_UNCLOAKED)] + [InlineAutoSubstituteData(PInvoke.EVENT_OBJECT_HIDE)] + [InlineAutoSubstituteData(PInvoke.EVENT_SYSTEM_MOVESIZESTART)] + [InlineAutoSubstituteData(PInvoke.EVENT_SYSTEM_MOVESIZEEND)] + [InlineAutoSubstituteData(PInvoke.EVENT_OBJECT_LOCATIONCHANGE)] + [InlineAutoSubstituteData(PInvoke.EVENT_SYSTEM_MINIMIZESTART)] + [InlineAutoSubstituteData(PInvoke.EVENT_SYSTEM_MINIMIZEEND)] + public void ShouldBeIgnored_UntilCloaked(uint eventType, IContext ctx, IWindow window) + { + // Given an event which isn't `EVENT_OBJECT_CLOAKED` or `EVENT_OBJECT_DESTROY` + window.ProcessFileName.Returns("firefox.exe"); + IWindowProcessor processor = FirefoxWindowProcessor.Create(ctx, window)!; + + // When the event is passed to `ShouldBeIgnored` + WindowProcessorResult result = processor.ProcessEvent(eventType, 0, 0, 0, 0); + + // Then the event should be ignored + Assert.Equal(WindowProcessorResult.Ignore, result); + } + + [Theory, AutoSubstituteData] + public void ShouldBeIgnored_FirstCloaked(IContext ctx, IWindow window) + { + // Given the first `EVENT_OBJECT_CLOAKED` event + window.ProcessFileName.Returns("firefox.exe"); + IWindowProcessor processor = FirefoxWindowProcessor.Create(ctx, window)!; + + // When the event is passed to `ShouldBeIgnored` + WindowProcessorResult result = processor.ProcessEvent(PInvoke.EVENT_OBJECT_CLOAKED, 0, 0, 0, 0); + + // Then the event should be ignored + Assert.Equal(WindowProcessorResult.Ignore, result); + } + + [Theory, AutoSubstituteData] + public void ShouldNotBeIgnored_SecondCloaked(IContext ctx, IWindow window) + { + // Given the second `EVENT_OBJECT_CLOAKED` event + window.ProcessFileName.Returns("firefox.exe"); + IWindowProcessor processor = FirefoxWindowProcessor.Create(ctx, window)!; + processor.ProcessEvent(PInvoke.EVENT_OBJECT_CLOAKED, 0, 0, 0, 0); + + // When the event is passed to `ShouldBeIgnored` + WindowProcessorResult result = processor.ProcessEvent(PInvoke.EVENT_OBJECT_CLOAKED, 0, 0, 0, 0); + + // Then the event should not be ignored + Assert.Equal(WindowProcessorResult.Process, result); + } + + [Theory, AutoSubstituteData] + public void ShouldBeRemoved(IContext ctx, IWindow window) + { + // Given an `EVENT_OBJECT_DESTROY` event + window.ProcessFileName.Returns("firefox.exe"); + IWindowProcessor processor = FirefoxWindowProcessor.Create(ctx, window)!; + + // When the event is passed to `ShouldBeIgnored` + WindowProcessorResult result = processor.ProcessEvent(PInvoke.EVENT_OBJECT_DESTROY, 0, 0, 0, 0); + + // Then the processor should be removed + Assert.Equal(WindowProcessorResult.RemoveProcessor, result); + } + + [Theory, AutoSubstituteData] + public void Window(IContext ctx, IWindow window) + { + // Given a window which is a Firefox window + window.ProcessFileName.Returns("firefox.exe"); + IWindowProcessor processor = FirefoxWindowProcessor.Create(ctx, window)!; + + // When `Window` is called + IWindow result = processor.Window; + + // Then the result should be the window + Assert.Equal(window, result); + } +} diff --git a/src/Whim.Tests/Store/WindowSector/Processors/WindowProcessorManagerTests.cs b/src/Whim.Tests/Store/WindowSector/Processors/WindowProcessorManagerTests.cs new file mode 100644 index 000000000..298dc5df8 --- /dev/null +++ b/src/Whim.Tests/Store/WindowSector/Processors/WindowProcessorManagerTests.cs @@ -0,0 +1,66 @@ +using Windows.Win32; + +namespace Whim.Tests; + +public class WindowProcessorManagerTests +{ + [Theory, AutoSubstituteData] + public void ShouldBeIgnored_CreateProcessor_Failure(IContext ctx, IWindow window) + { + // Given a window which is not a Firefox window + window.ProcessFileName.Returns("chrome.exe"); + WindowProcessorManager sut = new(ctx); + + // When ShouldBeIgnored is called + bool result = sut.ShouldBeIgnored(window, default, default, default, default, default, default); + + // Then the result should be false + Assert.False(result); + } + + [Theory, AutoSubstituteData] + public void ShouldBeIgnored_CreateProcessor_Success(IContext ctx, IWindow window) + { + // Given a window which is a Firefox window + window.ProcessFileName.Returns("firefox.exe"); + WindowProcessorManager sut = new(ctx); + + // When ShouldBeIgnored is called for the first time + bool result = sut.ShouldBeIgnored(window, default, default, default, default, default, default); + + // Then the result should be true + Assert.True(result); + } + + [Theory, AutoSubstituteData] + public void ShouldBeIgnored_ProcessorExists_Process(IContext ctx, IWindow window) + { + // Given a window which is a Firefox window + window.ProcessFileName.Returns("firefox.exe"); + WindowProcessorManager sut = new(ctx); + + // When ShouldBeIgnored is called for the second time + sut.ShouldBeIgnored(window, default, PInvoke.EVENT_OBJECT_CLOAKED, 0, 0, 0, 0); + bool result = sut.ShouldBeIgnored(window, default, 0, 0, 0, 0, 0); + + // Then the processor should have been created by the second call, and the window should not be ignored + Assert.False(result); + } + + [Theory, AutoSubstituteData] + public void ShouldBeIgnored_ProcessorExists_RemoveProcessor(IContext ctx, IWindow window) + { + // Given a window which is a Firefox window + window.ProcessFileName.Returns("firefox.exe"); + WindowProcessorManager sut = new(ctx); + + // When ShouldBeIgnored is called for the second time + sut.ShouldBeIgnored(window, default, PInvoke.EVENT_OBJECT_CLOAKED, 0, 0, 0, 0); + bool firstProcessorResult = sut.ShouldBeIgnored(window, default, PInvoke.EVENT_OBJECT_DESTROY, 0, 0, 0, 0); + bool secondProcessorResult = sut.ShouldBeIgnored(window, default, 0, 0, 0, 0, 0); + + // Then the processor should have been removed by the second call, and the window should be ignored in the next call + Assert.False(firstProcessorResult); + Assert.True(secondProcessorResult); + } +} diff --git a/src/Whim.Tests/Store/WindowSector/Transforms/WindowMovedTransformTests.cs b/src/Whim.Tests/Store/WindowSector/Transforms/WindowMovedTransformTests.cs index 795b016f9..fadc9ac6f 100644 --- a/src/Whim.Tests/Store/WindowSector/Transforms/WindowMovedTransformTests.cs +++ b/src/Whim.Tests/Store/WindowSector/Transforms/WindowMovedTransformTests.cs @@ -56,46 +56,6 @@ internal void IsNotMoving_NullProcessFileName(IContext ctx, MutableRootSector mu // Given the window is not moving and the process file name is null mutableRootSector.WindowSector.IsMovingWindow = false; window.ProcessFileName.ReturnsNull(); - ctx.WindowManager.LocationRestoringFilterManager.ShouldBeIgnored(window).Returns(true); - - WindowMovedTransform sut = new(window); - - // When we dispatch the transform - var result = AssertDoesNotRaise(ctx, mutableRootSector, sut); - - // Then we get an empty result - Assert.True(result.IsSuccessful); - ctx.NativeManager.DidNotReceive().TryEnqueue(Arg.Any()); - } - - [Theory, AutoSubstituteData] - internal void IsNotMoving_IsHandledLocation(IContext ctx, MutableRootSector mutableRootSector, IWindow window) - { - // Given the window is not moving and the window is a handled restoring location window - mutableRootSector.WindowSector.IsMovingWindow = false; - mutableRootSector.WindowSector.HandledLocationRestoringWindows = ImmutableHashSet.Create(window.Handle); - ctx.WindowManager.LocationRestoringFilterManager.ShouldBeIgnored(window).Returns(true); - - WindowMovedTransform sut = new(window); - - // When we dispatch the transform - var result = AssertDoesNotRaise(ctx, mutableRootSector, sut); - - // Then we get an empty result - Assert.True(result.IsSuccessful); - ctx.NativeManager.DidNotReceive().TryEnqueue(Arg.Any()); - } - - [Theory, AutoSubstituteData] - internal void IsNotMoving_IgnoredByLocationRestoringFilter( - IContext ctx, - MutableRootSector mutableRootSector, - IWindow window - ) - { - // Given the window is not moving and the window is a handled restoring location window - mutableRootSector.WindowSector.IsMovingWindow = false; - ctx.WindowManager.LocationRestoringFilterManager.ShouldBeIgnored(window).Returns(false); WindowMovedTransform sut = new(window); @@ -117,9 +77,7 @@ IWindow window { // Given the window is not moving, we don't ignore the window moving event, but we can get the pos mutableRootSector.WindowSector.IsMovingWindow = false; - mutableRootSector.WindowSector.WindowMovedDelay = 0; mutableRootSector.WindowSector.IsLeftMouseButtonDown = true; - ctx.WindowManager.LocationRestoringFilterManager.ShouldBeIgnored(window).Returns(true); Setup_GetCursorPos(internalCtx); WindowMovedTransform sut = new(window); @@ -127,9 +85,8 @@ IWindow window // When we dispatch the transform (Result? result, Assert.RaisedEvent ev) = AssertRaises(ctx, mutableRootSector, sut); - // Then we get a resulting window, a NativeManager.TryEnqueue to restore a window's position, and a second TryEnqueue - // to dispatch the events. - ctx.NativeManager.Received(2).TryEnqueue(Arg.Any()); + // Then we get a resulting window and a second TryEnqueue to dispatch the events. + ctx.NativeManager.Received(1).TryEnqueue(Arg.Any()); Assert.True(result!.Value.IsSuccessful); Assert.Equal(new Point(1, 2), ev.Arguments.CursorDraggedPoint); Assert.Equal(window, ev.Arguments.Window); diff --git a/src/Whim.Tests/Store/WindowSector/WindowEventListenerTests.cs b/src/Whim.Tests/Store/WindowSector/WindowEventListenerTests.cs index 722d9cf58..f934ce7da 100644 --- a/src/Whim.Tests/Store/WindowSector/WindowEventListenerTests.cs +++ b/src/Whim.Tests/Store/WindowSector/WindowEventListenerTests.cs @@ -302,7 +302,7 @@ public static IEnumerable WinEventProcCasesData() [MemberAutoSubstituteData(nameof(WinEventProcCasesData))] [Theory] - internal void WindowFocusedTransform( + internal void WinEventProc_Dispatch( uint ev, Func createTransform, IContext ctx, @@ -323,6 +323,26 @@ IInternalContext internalCtx ctx.Store.Received(1).Dispatch(createTransform(window)); } + [Theory, AutoSubstituteData] + internal void WinEventProc_Ignore(IContext ctx, IInternalContext internalCtx, IWindow window) + { + // Given the window is a Firefox window + window.Handle.Returns((HWND)1); + window.ProcessFileName.Returns("firefox.exe"); + ctx.Store.Pick(Arg.Any>>()).Returns(Result.FromValue(window)); + + CaptureWinEventProc capture = CaptureWinEventProc.Create(internalCtx); + + WindowEventListener sut = new(ctx, internalCtx); + sut.Initialize(); + + // When we send through the event + capture.WinEventProc!.Invoke((HWINEVENTHOOK)0, PInvoke.EVENT_OBJECT_SHOW, window.Handle, 0, 0, 0, 0); + + // Then nothing happens + AssertDispatches(ctx); + } + [Theory, AutoSubstituteData] internal void Default(IContext ctx, IInternalContext internalCtx) { diff --git a/src/Whim/Filter/DefaultFilteredWindows.cs b/src/Whim/Filter/DefaultFilteredWindows.cs index d14fc1a90..1347e324f 100644 --- a/src/Whim/Filter/DefaultFilteredWindows.cs +++ b/src/Whim/Filter/DefaultFilteredWindows.cs @@ -13,12 +13,4 @@ public static void LoadWindowsIgnoredByWhim(IFilterManager filterManager) { DefaultFilteredWindowsKomorebi.LoadWindowsIgnoredByKomorebi(filterManager); } - - /// - /// Load the windows which try to set their own locations when the start up. - /// See - /// - /// - public static void LoadLocationRestoringWindows(IFilterManager filterManager) => - filterManager.AddProcessFileNameFilter("firefox.exe").AddProcessFileNameFilter("gateway64.exe"); } diff --git a/src/Whim/Native/HWND.cs b/src/Whim/Native/HWND.cs index 6eb1a48ea..ea9e3fcaf 100644 --- a/src/Whim/Native/HWND.cs +++ b/src/Whim/Native/HWND.cs @@ -16,7 +16,10 @@ namespace Foundation internal static HWND Null => default; - internal bool IsNull => Value == default; + /// + /// Whether the handle has a zero value. + /// + public bool IsNull => Value == default; /// public static implicit operator IntPtr(HWND value) => value.Value; diff --git a/src/Whim/Store/Transform.cs b/src/Whim/Store/Transform.cs index 5cf8b6aab..a7312be38 100644 --- a/src/Whim/Store/Transform.cs +++ b/src/Whim/Store/Transform.cs @@ -3,7 +3,6 @@ namespace Whim; /// /// Operation describing how to update the state of the . /// The implementing record should be populated with the payload. -/// will specify how to update the store. /// /// public abstract record Transform diff --git a/src/Whim/Store/WindowSector/IWindowSector.cs b/src/Whim/Store/WindowSector/IWindowSector.cs index d77c70727..7c66a6432 100644 --- a/src/Whim/Store/WindowSector/IWindowSector.cs +++ b/src/Whim/Store/WindowSector/IWindowSector.cs @@ -10,12 +10,6 @@ public interface IWindowSector /// ImmutableDictionary Windows { get; } - /// - /// The windows which had their first location change event handled - see . - /// We maintain a set of the windows that have been handled so that we don't enter an infinite loop of location change events. - /// - ImmutableHashSet HandledLocationRestoringWindows { get; } - /// /// Whether a window is currently moving. /// @@ -26,9 +20,4 @@ public interface IWindowSector /// Used for window movement. /// bool IsLeftMouseButtonDown { get; } - - /// - /// The delay to wait when trying to restore windows from . - /// - int WindowMovedDelay { get; } } diff --git a/src/Whim/Store/WindowSector/Processors/FirefoxWindowProcessor.cs b/src/Whim/Store/WindowSector/Processors/FirefoxWindowProcessor.cs new file mode 100644 index 000000000..4ad5b4989 --- /dev/null +++ b/src/Whim/Store/WindowSector/Processors/FirefoxWindowProcessor.cs @@ -0,0 +1,90 @@ +using Windows.Win32; + +namespace Whim; + +/// +/// Custom logic to handle events from Firefox. +/// +public class FirefoxWindowProcessor : IWindowProcessor +{ + private bool _hasSeenFirstCloaked; + + /// + public IWindow Window { get; } + + private FirefoxWindowProcessor(IWindow window) + { + Window = window; + } + + /// + /// Creates a new instance of the implementing class, if the given window matches the processor. + /// + public static IWindowProcessor? Create(IContext ctx, IWindow window) => + window.ProcessFileName == "firefox.exe" ? new FirefoxWindowProcessor(window) : null; + + /// + /// Indicates whether the event should be ignored by Whim. + /// + /// + /// Firefox has some irregular behavior: + /// + /// + /// + /// + /// + /// Firefox will move a window after an interdeterminate timeout on startup [Source] + /// + /// + /// + /// + /// + /// Firefox can perform actions 500ms after an event is received - e.g., after WindowMoved [Source] + /// + /// + /// + /// + /// + /// Firefox will cloak the window when showing for the first time [Source] + /// + /// + /// + /// + /// To deal with these issues, we ignore all events until the first event is received. + /// + /// + /// + /// + /// + /// + /// + public WindowProcessorResult ProcessEvent( + uint eventType, + int idObject, + int idChild, + uint idEventThread, + uint dwmsEventTime + ) + { + if (eventType == PInvoke.EVENT_OBJECT_CLOAKED) + { + if (!_hasSeenFirstCloaked) + { + _hasSeenFirstCloaked = true; + return WindowProcessorResult.Ignore; + } + } + + if (eventType == PInvoke.EVENT_OBJECT_DESTROY) + { + return WindowProcessorResult.RemoveProcessor; + } + + if (!_hasSeenFirstCloaked) + { + return WindowProcessorResult.Ignore; + } + + return WindowProcessorResult.Process; + } +} diff --git a/src/Whim/Store/WindowSector/Processors/IWindowProcessor.cs b/src/Whim/Store/WindowSector/Processors/IWindowProcessor.cs new file mode 100644 index 000000000..1ed35fb90 --- /dev/null +++ b/src/Whim/Store/WindowSector/Processors/IWindowProcessor.cs @@ -0,0 +1,35 @@ +namespace Whim; + +/// +/// Represents a processor for events from Windows. This is for windows with non-standard behavior. +/// For example, Firefox will try reset the window position on startup. The +/// will ignore these events. +/// +public interface IWindowProcessor +{ + /// + /// The window that this processor is for. + /// + public IWindow Window { get; } + + /// + /// Processes the given event. + /// + /// For more about the arguments, see https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wineventproc + /// + /// + /// + /// + /// + /// + /// + /// Whether the event should be ignored by Whim. + /// + public abstract WindowProcessorResult ProcessEvent( + uint eventType, + int idObject, + int idChild, + uint idEventThread, + uint dwmsEventTime + ); +} diff --git a/src/Whim/Store/WindowSector/Processors/WindowProcessorManager.cs b/src/Whim/Store/WindowSector/Processors/WindowProcessorManager.cs new file mode 100644 index 000000000..aec5676e4 --- /dev/null +++ b/src/Whim/Store/WindowSector/Processors/WindowProcessorManager.cs @@ -0,0 +1,74 @@ +using Windows.Win32.UI.Accessibility; + +namespace Whim; + +internal class WindowProcessorManager +{ + private readonly IContext _ctx; + + private readonly List> _processorCreators = new(); + + private readonly Dictionary _processors = new(); + + public WindowProcessorManager(IContext ctx) + { + _ctx = ctx; + _processorCreators.Add(FirefoxWindowProcessor.Create); + } + + internal bool ShouldBeIgnored( + IWindow window, + HWINEVENTHOOK hWinEventHook, + uint eventType, + int idObject, + int idChild, + uint idEventThread, + uint dwmsEventTime + ) + { + if (!_processors.TryGetValue(window.Handle, out IWindowProcessor? processor)) + { + if ((processor = CreateProcessor(window)) == null) + { + // No processor was created for this window, so we don't have any special handling for it. + return false; + } + } + + WindowProcessorResult result = processor.ProcessEvent( + eventType, + idObject, + idChild, + idEventThread, + dwmsEventTime + ); + switch (result) + { + case WindowProcessorResult.Process: + return false; + case WindowProcessorResult.Ignore: + return true; + case WindowProcessorResult.RemoveProcessor: + _processors.Remove(window.Handle); + return false; + default: + Logger.Error($"Unhandled WindowProcessorResult {result}"); + return false; + } + } + + private IWindowProcessor? CreateProcessor(IWindow window) + { + foreach (Func creator in _processorCreators) + { + IWindowProcessor? processor = creator(_ctx, window); + if (processor != null) + { + _processors.Add(window.Handle, processor); + return processor; + } + } + + return null; + } +} diff --git a/src/Whim/Store/WindowSector/Processors/WindowProcessorResult.cs b/src/Whim/Store/WindowSector/Processors/WindowProcessorResult.cs new file mode 100644 index 000000000..1025d4ccf --- /dev/null +++ b/src/Whim/Store/WindowSector/Processors/WindowProcessorResult.cs @@ -0,0 +1,22 @@ +namespace Whim; + +/// +/// The result of processing a window event by a . +/// +public enum WindowProcessorResult +{ + /// + /// The event should be ignored. + /// + Ignore, + + /// + /// The event should be processed. + /// + Process, + + /// + /// The event should be processed and the processor should be removed. + /// + RemoveProcessor +} diff --git a/src/Whim/Store/WindowSector/Transforms/WindowMovedTransform.cs b/src/Whim/Store/WindowSector/Transforms/WindowMovedTransform.cs index b2385009a..7993f06f8 100644 --- a/src/Whim/Store/WindowSector/Transforms/WindowMovedTransform.cs +++ b/src/Whim/Store/WindowSector/Transforms/WindowMovedTransform.cs @@ -12,29 +12,9 @@ MutableRootSector mutableRootSector { WindowSector windowSector = mutableRootSector.WindowSector; - if (!windowSector.IsMovingWindow) + if (!windowSector.IsMovingWindow && Window.ProcessFileName == null) { - if ( - Window.ProcessFileName == null - || windowSector.HandledLocationRestoringWindows.Contains(Window.Handle) - || !ctx.WindowManager.LocationRestoringFilterManager.ShouldBeIgnored(Window) - ) - { - // Ignore the window moving event. - return Unit.Result; - } - - // The window's application tried to restore its position. - // Wait, then restore the position. - ctx.NativeManager.TryEnqueue(async () => - { - await Task.Delay(windowSector.WindowMovedDelay).ConfigureAwait(true); - if (ctx.Store.Pick(PickWorkspaceByWindow(Window.Handle)).TryGet(out IWorkspace workspace)) - { - windowSector.HandledLocationRestoringWindows.Add(Window.Handle); - workspace.DoLayout(); - } - }); + return Unit.Result; } IPoint? cursorPoint = null; diff --git a/src/Whim/Store/WindowSector/Transforms/WindowRemovedTransform.cs b/src/Whim/Store/WindowSector/Transforms/WindowRemovedTransform.cs index d8c68f955..cc0d18feb 100644 --- a/src/Whim/Store/WindowSector/Transforms/WindowRemovedTransform.cs +++ b/src/Whim/Store/WindowSector/Transforms/WindowRemovedTransform.cs @@ -32,9 +32,6 @@ MutableRootSector mutableRootSector private void UpdateWindowSector(WindowSector windowSector) { windowSector.Windows = windowSector.Windows.Remove(Window.Handle); - windowSector.HandledLocationRestoringWindows = windowSector.HandledLocationRestoringWindows.Remove( - Window.Handle - ); windowSector.QueueEvent(new WindowRemovedEventArgs() { Window = Window }); } diff --git a/src/Whim/Store/WindowSector/WindowEventListener.cs b/src/Whim/Store/WindowSector/WindowEventListener.cs index 478594578..3afb31878 100644 --- a/src/Whim/Store/WindowSector/WindowEventListener.cs +++ b/src/Whim/Store/WindowSector/WindowEventListener.cs @@ -20,11 +20,14 @@ internal class WindowEventListener : IDisposable /// private readonly WINEVENTPROC _hookDelegate; + private readonly WindowProcessorManager _processorManager; + public WindowEventListener(IContext ctx, IInternalContext internalCtx) { _ctx = ctx; _internalCtx = internalCtx; _hookDelegate = new WINEVENTPROC(WinEventProcWrapper); + _processorManager = new WindowProcessorManager(ctx); } public void Initialize() @@ -132,6 +135,22 @@ uint _dwmsEventTime } } + if ( + _processorManager.ShouldBeIgnored( + window, + _hWinEventHook, + eventType, + idObject, + idChild, + _idEventThread, + _dwmsEventTime + ) + ) + { + Logger.Debug("Ignoring event for window due to processor"); + return; + } + Logger.Debug($"Windows event 0x{eventType:X4} for {window}"); switch (eventType) { diff --git a/src/Whim/Store/WindowSector/WindowSector.cs b/src/Whim/Store/WindowSector/WindowSector.cs index efb4a2d25..780ff75d5 100644 --- a/src/Whim/Store/WindowSector/WindowSector.cs +++ b/src/Whim/Store/WindowSector/WindowSector.cs @@ -10,14 +10,10 @@ internal class WindowSector : SectorBase, IWindowSector, IDisposable, IWindowSec public ImmutableDictionary Windows { get; internal set; } = ImmutableDictionary.Empty; - public ImmutableHashSet HandledLocationRestoringWindows { get; internal set; } = ImmutableHashSet.Empty; - public bool IsMovingWindow { get; internal set; } public bool IsLeftMouseButtonDown { get; internal set; } - public int WindowMovedDelay { get; internal set; } = 2000; - public event EventHandler? WindowAdded; public event EventHandler? WindowFocused; public event EventHandler? WindowRemoved; diff --git a/src/Whim/Window/IWindowManager.cs b/src/Whim/Window/IWindowManager.cs index 708b890c4..d19f05356 100644 --- a/src/Whim/Window/IWindowManager.cs +++ b/src/Whim/Window/IWindowManager.cs @@ -5,11 +5,6 @@ namespace Whim; /// public interface IWindowManager : IEnumerable, IDisposable { - /// - /// Filters for windows that will try restore window locations after their windows are created. - /// - IFilterManager LocationRestoringFilterManager { get; } - /// /// Initialize the windows event hooks. /// diff --git a/src/Whim/Window/WindowManager.cs b/src/Whim/Window/WindowManager.cs index b20a5503d..9383691c9 100644 --- a/src/Whim/Window/WindowManager.cs +++ b/src/Whim/Window/WindowManager.cs @@ -21,16 +21,10 @@ internal class WindowManager : IWindowManager, IInternalWindowManager /// private bool _disposedValue; - public IFilterManager LocationRestoringFilterManager { get; } = new FilterManager(); - - internal int WindowMovedDelay { get; init; } = 2000; - public WindowManager(IContext context, IInternalContext internalContext) { _context = context; _internalContext = internalContext; - - DefaultFilteredWindows.LoadLocationRestoringWindows(LocationRestoringFilterManager); } public void Initialize()