From 742b611c2b5bddd832b6038f2f3a3ca1b13d1d1e Mon Sep 17 00:00:00 2001 From: Lars Berger Date: Thu, 14 Sep 2023 01:15:54 +0800 Subject: [PATCH] feat: focus override based on timer --- .../CommandHandlers/SyncNativeFocusHandler.cs | 9 +---- GlazeWM.Domain/Containers/ContainerService.cs | 10 ++--- .../CommandHandlers/UnmanageWindowHandler.cs | 15 +++++--- .../EventHandlers/WindowFocusedHandler.cs | 38 ++++--------------- .../EventHandlers/WindowMinimizedHandler.cs | 16 ++++++-- GlazeWM.Domain/Windows/WindowService.cs | 15 ++------ GlazeWM.Infrastructure/Utils/Countdown.cs | 23 +++++++++++ 7 files changed, 63 insertions(+), 63 deletions(-) create mode 100644 GlazeWM.Infrastructure/Utils/Countdown.cs diff --git a/GlazeWM.Domain/Containers/CommandHandlers/SyncNativeFocusHandler.cs b/GlazeWM.Domain/Containers/CommandHandlers/SyncNativeFocusHandler.cs index 26cff51a5..0b6685666 100644 --- a/GlazeWM.Domain/Containers/CommandHandlers/SyncNativeFocusHandler.cs +++ b/GlazeWM.Domain/Containers/CommandHandlers/SyncNativeFocusHandler.cs @@ -12,16 +12,11 @@ internal sealed class SyncNativeFocusHandler : ICommandHandler window.Handle, - Workspace => _windowService.DesktopWindowHandle, + Workspace => GetDesktopWindow(), _ => throw new Exception("Invalid container type to focus. This is a bug."), }; diff --git a/GlazeWM.Domain/Containers/ContainerService.cs b/GlazeWM.Domain/Containers/ContainerService.cs index e7b68ed0d..ef87a552d 100644 --- a/GlazeWM.Domain/Containers/ContainerService.cs +++ b/GlazeWM.Domain/Containers/ContainerService.cs @@ -40,15 +40,15 @@ public class ContainerService FocusMode.Floating : FocusMode.Tiling; /// - /// Name of the currently active binding mode (if one is active). + /// While active, any this container overrides the target container to set focus to on the next + /// focus window event (ie. `EVENT_SYSTEM_FOREGROUND`). /// - public string ActiveBindingMode { get; set; } + public Countdown FocusOverrideCountdown { get; set; } = new(TimeSpan.FromMilliseconds(100)); /// - /// If set, this container overrides the target container to set focus to on the next - /// focus window event (ie. `EVENT_SYSTEM_FOREGROUND`). + /// Name of the currently active binding mode (if one is active). /// - public Container PendingFocusContainer { get; set; } + public string ActiveBindingMode { get; set; } private readonly UserConfigService _userConfigService; diff --git a/GlazeWM.Domain/Windows/CommandHandlers/UnmanageWindowHandler.cs b/GlazeWM.Domain/Windows/CommandHandlers/UnmanageWindowHandler.cs index 5e2b56b14..e56a9ea12 100644 --- a/GlazeWM.Domain/Windows/CommandHandlers/UnmanageWindowHandler.cs +++ b/GlazeWM.Domain/Windows/CommandHandlers/UnmanageWindowHandler.cs @@ -22,23 +22,28 @@ public CommandResponse Handle(UnmanageWindowCommand command) var window = command.Window; // Get container to switch focus to after the window has been removed. - var focusTarget = WindowService.GetFocusTargetAfterRemoval(window); + var focusedContainer = _containerService.FocusedContainer; + var focusTarget = window == focusedContainer + ? WindowService.GetFocusTargetAfterRemoval(window) + : null; if (window is IResizable) _bus.Invoke(new DetachAndResizeContainerCommand(window)); else _bus.Invoke(new DetachContainerCommand(window)); + _bus.Emit(new WindowUnmanagedEvent(window.Id, window.Handle)); + + if (focusTarget is null) + return CommandResponse.Ok; + // The OS automatically switches focus to a different window after closing. If // there are focusable windows, then set focus *after* the OS sets focus. This will // cause focus to briefly flicker to the OS focus target and then to the WM's focus // target. - _bus.Invoke(new SetFocusedDescendantCommand(focusTarget)); - _containerService.PendingFocusContainer = focusTarget; _containerService.HasPendingFocusSync = true; - - _bus.Emit(new WindowUnmanagedEvent(window.Id, window.Handle)); + _windowService.UnmanagedOrMinimizedStopwatch.Restart(); return CommandResponse.Ok; } diff --git a/GlazeWM.Domain/Windows/EventHandlers/WindowFocusedHandler.cs b/GlazeWM.Domain/Windows/EventHandlers/WindowFocusedHandler.cs index 02b891d74..80ee95586 100644 --- a/GlazeWM.Domain/Windows/EventHandlers/WindowFocusedHandler.cs +++ b/GlazeWM.Domain/Windows/EventHandlers/WindowFocusedHandler.cs @@ -32,36 +32,6 @@ public WindowFocusedHandler( public void Handle(WindowFocusedEvent @event) { var windowHandle = @event.WindowHandle; - var pendingFocusContainer = _containerService.PendingFocusContainer; - - // Override the container to set focus to (ie. when changing focus after a window is - // closed or hidden). - // TODO: This can be simplified a lot. Might also need to handle case where window - // is null or not displayed. - if (pendingFocusContainer is not null) - { - // If the container gaining focus is the pending focus container, then reset it. - if (pendingFocusContainer is Window && @event.WindowHandle == (pendingFocusContainer as Window).Handle) - { - _bus.Invoke(new SetFocusedDescendantCommand(pendingFocusContainer)); - _bus.Emit(new FocusChangedEvent(pendingFocusContainer)); - _containerService.PendingFocusContainer = null; - return; - } - - var isDesktopWindow = windowHandle == _windowService.DesktopWindowHandle; - - if (pendingFocusContainer is Workspace && isDesktopWindow) - { - _bus.Invoke(new SetFocusedDescendantCommand(pendingFocusContainer)); - _bus.Emit(new FocusChangedEvent(pendingFocusContainer)); - _containerService.PendingFocusContainer = null; - return; - } - - _bus.InvokeAsync(new SyncNativeFocusCommand()); - return; - } var window = _windowService.GetWindows() .FirstOrDefault(window => window.Handle == @event.WindowHandle); @@ -77,6 +47,14 @@ public void Handle(WindowFocusedEvent @event) if (window == focusedContainer) return; + var unmanagedStopwatch = _windowService.UnmanagedOrMinimizedStopwatch; + + if (unmanagedStopwatch?.ElapsedMilliseconds < 100) + { + _bus.Invoke(new SyncNativeFocusCommand()); + return; + } + // Update the WM's focus state. _bus.Invoke(new SetFocusedDescendantCommand(window)); _bus.Emit(new FocusChangedEvent(window)); diff --git a/GlazeWM.Domain/Windows/EventHandlers/WindowMinimizedHandler.cs b/GlazeWM.Domain/Windows/EventHandlers/WindowMinimizedHandler.cs index 75e4b02fa..848083e2f 100644 --- a/GlazeWM.Domain/Windows/EventHandlers/WindowMinimizedHandler.cs +++ b/GlazeWM.Domain/Windows/EventHandlers/WindowMinimizedHandler.cs @@ -55,14 +55,22 @@ public void Handle(WindowMinimizedEvent @event) Id = window.Id }; - // Get container to switch focus to after the window has been minimized. - var focusTarget = WindowService.GetFocusTargetAfterRemoval(window); + // Get container to switch focus to after the window has been minimized. Need to + // get focus target prior to calling `ReplaceContainerCommand`. + var focusedContainer = _containerService.FocusedContainer; + var focusTarget = window == focusedContainer + ? WindowService.GetFocusTargetAfterRemoval(window) + : null; _bus.Invoke(new ReplaceContainerCommand(minimizedWindow, window.Parent, window.Index)); // Focus should be reassigned to appropriate container. - _bus.Invoke(new SetFocusedDescendantCommand(focusTarget)); - _containerService.HasPendingFocusSync = true; + if (focusTarget is not null) + { + _bus.Invoke(new SetFocusedDescendant(focusTarget)); + _containerService.HasPendingFocusSync = true; + _windowService.UnmanagedOrMinimizedStopwatch.Restart(); + } _containerService.ContainersToRedraw.Add(workspace); _bus.Invoke(new RedrawContainersCommand()); diff --git a/GlazeWM.Domain/Windows/WindowService.cs b/GlazeWM.Domain/Windows/WindowService.cs index 864445533..9bc92567c 100644 --- a/GlazeWM.Domain/Windows/WindowService.cs +++ b/GlazeWM.Domain/Windows/WindowService.cs @@ -26,9 +26,10 @@ public class WindowService public List IgnoredHandles { get; set; } = new(); /// - /// Handle to the desktop window. + /// Time since a previously focused window was unmanaged or minimized. Used to + /// decide whether to override incoming focus events. /// - public IntPtr DesktopWindowHandle { get; } = GetDesktopWindowHandle(); + public Stopwatch UnmanagedOrMinimizedStopwatch { get; } = new(); private readonly ContainerService _containerService; private readonly MonitorService _monitorService; @@ -127,16 +128,6 @@ public static List GetAllWindowHandles() return windowHandles; } - private static IntPtr GetDesktopWindowHandle() - { - return GetAllWindowHandles().Find(handle => - { - var className = GetClassNameOfHandle(handle); - var process = GetProcessOfHandle(handle); - return className == "Progman" && process.ProcessName == "explorer"; - }); - } - public static WindowStylesEx GetWindowStylesEx(IntPtr handle) { return unchecked((WindowStylesEx)GetWindowLongPtr(handle, GWLEXSTYLE).ToInt64()); diff --git a/GlazeWM.Infrastructure/Utils/Countdown.cs b/GlazeWM.Infrastructure/Utils/Countdown.cs new file mode 100644 index 000000000..452e442b0 --- /dev/null +++ b/GlazeWM.Infrastructure/Utils/Countdown.cs @@ -0,0 +1,23 @@ +namespace GlazeWM.Infrastructure.Utils +{ + public class Countdown + { + public TimeSpan _timeSpan { get; init; } + private DateTime _startTime { get; set; } + + public Countdown(TimeSpan timeSpan) + { + _timeSpan = timeSpan; + } + + public void Start() + { + _startTime = DateTime.Now; + } + + public bool HasElapsed() + { + return (DateTime.Now - _startTime).TotalMilliseconds > _timeSpan.Milliseconds; + } + } +}