Skip to content

Commit

Permalink
feat: focus override based on timer
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-berger committed Sep 13, 2023
1 parent 0eff0ed commit 742b611
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,11 @@ internal sealed class SyncNativeFocusHandler : ICommandHandler<SyncNativeFocusCo
{
private readonly Bus _bus;
private readonly ContainerService _containerService;
private readonly WindowService _windowService;

public SyncNativeFocusHandler(
Bus bus,
ContainerService containerService,
WindowService windowService)
public SyncNativeFocusHandler(Bus bus, ContainerService containerService)
{
_bus = bus;
_containerService = containerService;
_windowService = windowService;
}

public CommandResponse Handle(SyncNativeFocusCommand command)
Expand All @@ -37,7 +32,7 @@ public CommandResponse Handle(SyncNativeFocusCommand command)
var handleToFocus = focusedContainer switch
{
Window window => window.Handle,
Workspace => _windowService.DesktopWindowHandle,
Workspace => GetDesktopWindow(),
_ => throw new Exception("Invalid container type to focus. This is a bug."),
};

Expand Down
10 changes: 5 additions & 5 deletions GlazeWM.Domain/Containers/ContainerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ public class ContainerService
FocusMode.Floating : FocusMode.Tiling;

/// <summary>
/// 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`).
/// </summary>
public string ActiveBindingMode { get; set; }
public Countdown FocusOverrideCountdown { get; set; } = new(TimeSpan.FromMilliseconds(100));

/// <summary>
/// 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).
/// </summary>
public Container PendingFocusContainer { get; set; }
public string ActiveBindingMode { get; set; }

private readonly UserConfigService _userConfigService;

Expand Down
15 changes: 10 additions & 5 deletions GlazeWM.Domain/Windows/CommandHandlers/UnmanageWindowHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
38 changes: 8 additions & 30 deletions GlazeWM.Domain/Windows/EventHandlers/WindowFocusedHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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));
Expand Down
16 changes: 12 additions & 4 deletions GlazeWM.Domain/Windows/EventHandlers/WindowMinimizedHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
15 changes: 3 additions & 12 deletions GlazeWM.Domain/Windows/WindowService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ public class WindowService
public List<IntPtr> IgnoredHandles { get; set; } = new();

/// <summary>
/// Handle to the desktop window.
/// Time since a previously focused window was unmanaged or minimized. Used to
/// decide whether to override incoming focus events.
/// </summary>
public IntPtr DesktopWindowHandle { get; } = GetDesktopWindowHandle();
public Stopwatch UnmanagedOrMinimizedStopwatch { get; } = new();

private readonly ContainerService _containerService;
private readonly MonitorService _monitorService;
Expand Down Expand Up @@ -127,16 +128,6 @@ public static List<IntPtr> 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());
Expand Down
23 changes: 23 additions & 0 deletions GlazeWM.Infrastructure/Utils/Countdown.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}

0 comments on commit 742b611

Please sign in to comment.