Skip to content

Commit

Permalink
feat: reduce amount of calls to set native focus (#390)
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-berger authored Sep 13, 2023
1 parent add74f3 commit 12fd5b2
Show file tree
Hide file tree
Showing 25 changed files with 88 additions and 79 deletions.
25 changes: 11 additions & 14 deletions GlazeWM.App.WindowManager/WmStartup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public ExitCode Run()
// Populate initial monitors, windows, workspaces and user config.
_bus.Invoke(new PopulateInitialStateCommand());
_bus.Invoke(new RedrawContainersCommand());
_bus.Invoke(new SyncNativeFocusCommand());

// Listen on registered keybindings.
_keybindingService.Start();
Expand All @@ -75,22 +76,14 @@ public ExitCode Run()
_systemTrayIcon = new SystemTrayIcon(systemTrayIconConfig);
_systemTrayIcon.Show();

var nativeFocusReassigned = _bus.Events
.OfType<NativeFocusReassignedEvent>()
.Select((@event) => @event.FocusedContainer);

if (_userConfigService.FocusBorderConfig.Active.Enabled ||
_userConfigService.FocusBorderConfig.Inactive.Enabled)
{
var focusChanged = _bus.Events
.OfType<FocusChangedEvent>()
.Select(@event => @event.FocusedContainer);

focusChanged
.Merge(nativeFocusReassigned)
.Subscribe((container) =>
_bus.InvokeAsync(new SetActiveWindowBorderCommand(container as Window))
);
_bus.Events.OfType<FocusChangedEvent>().Subscribe((@event) =>
_bus.InvokeAsync(
new SetActiveWindowBorderCommand(@event.FocusedContainer as Window)
)
);
}

// Hook mouse event for focus follows cursor.
Expand All @@ -108,7 +101,11 @@ public ExitCode Run()
.OfType<FocusedContainerMovedEvent>()
.Select(@event => @event.FocusedContainer);

focusedContainerMoved.Merge(nativeFocusReassigned)
var nativeFocusSynced = _bus.Events
.OfType<NativeFocusSyncedEvent>()
.Select((@event) => @event.FocusedContainer);

focusedContainerMoved.Merge(nativeFocusSynced)
.Where(container => container is Window)
.Subscribe((window) => _bus.InvokeAsync(new CenterCursorOnContainerCommand(window)));
}
Expand Down
1 change: 1 addition & 0 deletions GlazeWM.Bar/Components/WorkspacesComponentViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public WorkspacesComponentViewModel(
@event is WorkspaceActivatedEvent
or WorkspaceDeactivatedEvent
or FocusChangedEvent
or FocusedContainerMovedEvent
);

// Refresh contents of workspaces collection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using GlazeWM.Domain.Common.Commands;
using GlazeWM.Domain.Containers;
using GlazeWM.Domain.Containers.Commands;
using GlazeWM.Domain.Containers.Events;
using GlazeWM.Domain.Monitors;
using GlazeWM.Domain.Monitors.Commands;
using GlazeWM.Domain.UserConfigs.Commands;
Expand All @@ -17,16 +16,20 @@ namespace GlazeWM.Domain.Common.CommandHandlers
internal sealed class PopulateInitialStateHandler : ICommandHandler<PopulateInitialStateCommand>
{
private readonly Bus _bus;
private readonly ContainerService _containerService;
private readonly MonitorService _monitorService;
private readonly WindowService _windowService;
private readonly WorkspaceService _workspaceService;

public PopulateInitialStateHandler(Bus bus,
public PopulateInitialStateHandler(
Bus bus,
ContainerService containerService,
MonitorService monitorService,
WindowService windowService,
WorkspaceService workspaceService)
{
_bus = bus;
_containerService = containerService;
_monitorService = monitorService;
_windowService = windowService;
_workspaceService = workspaceService;
Expand Down Expand Up @@ -58,8 +61,7 @@ public CommandResponse Handle(PopulateInitialStateCommand command)
_workspaceService.GetActiveWorkspaces().FirstOrDefault() as Container;

_bus.Invoke(new SetFocusedDescendantCommand(containerToFocus));
_bus.Emit(new FocusChangedEvent(containerToFocus));
_bus.Invoke(new SetNativeFocusCommand(containerToFocus));
_containerService.HasPendingFocusSync = true;

return CommandResponse.Ok;
}
Expand Down
2 changes: 1 addition & 1 deletion GlazeWM.Domain/Common/DomainEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public static class DomainEvent
public const string FocusedContainerMoved = "focused_container_moved";
public const string MonitorAdded = "monitor_added";
public const string MonitorRemoved = "monitor_removed";
public const string NativeFocusReassigned = "native_focus_reassigned";
public const string NativeFocusSynced = "native_focus_synced";
public const string TilingDirectionChanged = "tiling_direction_changed";
public const string UserConfigReloaded = "user_config_reloaded";
public const string WindowManaged = "window_managed";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ public CommandResponse Handle(FocusInDirectionCommand command)
if (focusTarget is null || focusTarget == focusedContainer)
return CommandResponse.Ok;

_bus.Invoke(new SetNativeFocusCommand(focusTarget));
_bus.Invoke(new SetFocusedDescendantCommand(focusTarget));
_containerService.HasPendingFocusSync = true;

return CommandResponse.Ok;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,46 @@

namespace GlazeWM.Domain.Containers.CommandHandlers
{
internal sealed class SetNativeFocusHandler : ICommandHandler<SetNativeFocusCommand>
internal sealed class SyncNativeFocusHandler : ICommandHandler<SyncNativeFocusCommand>
{
private readonly Bus _bus;
private readonly ContainerService _containerService;
private readonly WindowService _windowService;

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

public CommandResponse Handle(SetNativeFocusCommand command)
public CommandResponse Handle(SyncNativeFocusCommand command)
{
var containerToFocus = command.ContainerToFocus;
var hasPendingFocusSync = _containerService.HasPendingFocusSync;

var handleToFocus = containerToFocus switch
if (!hasPendingFocusSync)
return CommandResponse.Ok;

// Container that the WM believes should have focus.
var focusedContainer = _containerService.FocusedContainer;

var handleToFocus = focusedContainer switch
{
Window window => window.Handle,
Workspace => _windowService.DesktopWindowHandle,
_ => throw new Exception("Invalid container type to focus. This is a bug."),
};

// Set focus to the given window handle. If the container is a normal window, then this
// will trigger `EVENT_SYSTEM_FOREGROUND` window event and its handler. This, in turn,
// calls `SetFocusedDescendantCommand`.
// will trigger `EVENT_SYSTEM_FOREGROUND` window event and its handler.
KeybdEvent(0, 0, 0, 0);
SetForegroundWindow(handleToFocus);

_bus.Emit(new NativeFocusReassignedEvent(containerToFocus));

// Setting focus to the desktop window does not emit `EVENT_SYSTEM_FOREGROUND` window event,
// so `SetFocusedDescendantCommand` has to be manually called.
// TODO: This is called twice unnecessarily when setting workspace focus on unmanage.
if (containerToFocus is Workspace)
{
_bus.Invoke(new SetFocusedDescendantCommand(containerToFocus));
_bus.Emit(new FocusChangedEvent(containerToFocus));
}
_bus.Emit(new NativeFocusSyncedEvent(focusedContainer));
_bus.Emit(new FocusChangedEvent(focusedContainer));

return CommandResponse.Ok;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public CommandResponse Handle(ToggleFocusModeCommand command)
if (windowToFocus is null)
return CommandResponse.Ok;

_bus.Invoke(new SetNativeFocusCommand(windowToFocus));
_bus.Invoke(new SetFocusedDescendantCommand(windowToFocus));
_containerService.HasPendingFocusSync = true;

return CommandResponse.Ok;
}
Expand Down
14 changes: 0 additions & 14 deletions GlazeWM.Domain/Containers/Commands/SetNativeFocusCommand.cs

This file was deleted.

8 changes: 8 additions & 0 deletions GlazeWM.Domain/Containers/Commands/SyncNativeFocusCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using GlazeWM.Infrastructure.Bussing;

namespace GlazeWM.Domain.Containers.Commands
{
public class SyncNativeFocusCommand : Command
{
}
}
5 changes: 5 additions & 0 deletions GlazeWM.Domain/Containers/ContainerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public class ContainerService
/// </summary>
public Container FocusedContainer => ContainerTree.LastFocusedDescendant;

/// <summary>
/// Whether native focus needs to be reassigned to `FocusedContainer`.
/// </summary>
public bool HasPendingFocusSync { get; set; }

/// <summary>
/// Whether a tiling or floating container is currently focused.
/// </summary>
Expand Down

This file was deleted.

8 changes: 8 additions & 0 deletions GlazeWM.Domain/Containers/Events/NativeFocusSyncedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using GlazeWM.Domain.Common;
using GlazeWM.Infrastructure.Bussing;

namespace GlazeWM.Domain.Containers.Events
{
public record NativeFocusSyncedEvent(Container FocusedContainer)
: Event(DomainEvent.NativeFocusSynced);
}
2 changes: 1 addition & 1 deletion GlazeWM.Domain/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static IServiceCollection AddDomainServices(this IServiceCollection servi
services.AddSingleton<ICommandHandler<ReplaceContainerCommand>, ReplaceContainerHandler>();
services.AddSingleton<ICommandHandler<ResizeContainerCommand>, ResizeContainerHandler>();
services.AddSingleton<ICommandHandler<SetFocusedDescendantCommand>, SetFocusedDescendantHandler>();
services.AddSingleton<ICommandHandler<SetNativeFocusCommand>, SetNativeFocusHandler>();
services.AddSingleton<ICommandHandler<SyncNativeFocusCommand>, SyncNativeFocusHandler>();
services.AddSingleton<ICommandHandler<ToggleFocusModeCommand>, ToggleFocusModeHandler>();
services.AddSingleton<ICommandHandler<AddMonitorCommand>, AddMonitorHandler>();
services.AddSingleton<ICommandHandler<RefreshMonitorStateCommand>, RefreshMonitorStateHandler>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public CommandResponse Handle(RegisterKeybindingsCommand command)

_bus.Invoke(new RunWithSubjectContainerCommand(commandStrings));
_bus.Invoke(new RedrawContainersCommand());
_bus.Invoke(new SyncNativeFocusCommand());
}
}
catch (Exception e)
Expand Down
4 changes: 2 additions & 2 deletions GlazeWM.Domain/Windows/CommandHandlers/ManageWindowHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ public CommandResponse Handle(ManageWindowCommand command)
_logger.LogWindowEvent("New window managed", window);
_bus.Emit(new WindowManagedEvent(window));

// Set OS focus to the newly added window in case it's not already focused.
_bus.Invoke(new SetNativeFocusCommand(window));
// OS focus should be set to the newly added window in case it's not already focused.
_containerService.HasPendingFocusSync = true;

return CommandResponse.Ok;
}
Expand Down
4 changes: 0 additions & 4 deletions GlazeWM.Domain/Windows/CommandHandlers/MoveWindowHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,6 @@ private void MoveToWorkspaceInDirection(Window windowToMove, Direction direction
_bus.Invoke(new MoveContainerWithinTreeCommand(windowToMove, workspaceInDirection, true));
else
_bus.Invoke(new MoveContainerWithinTreeCommand(windowToMove, workspaceInDirection, 0, true));

// Refresh state in bar of which workspace has focus.
_bus.Emit(new FocusChangedEvent(windowToMove));
}

private void ChangeWorkspaceTilingDirection(Window windowToMove, Direction direction)
Expand Down Expand Up @@ -289,7 +286,6 @@ private void MoveFloatingWindow(Window windowToMove, Direction direction)

// Change the window's parent workspace.
_bus.Invoke(new MoveContainerWithinTreeCommand(windowToMove, workspaceInDirection, false));
_bus.Emit(new FocusChangedEvent(windowToMove));

// Redrawing twice to fix weird WindowsOS dpi behaviour
windowToMove.HasPendingDpiAdjustment = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ private void ResizeFloatingWindow(Window windowToResize, ResizeDimension dimensi

// Change the window's parent workspace.
_bus.Invoke(new MoveContainerWithinTreeCommand(windowToResize, targetWorkspace, false));
_bus.Emit(new FocusChangedEvent(windowToResize));

windowToResize.HasPendingDpiAdjustment = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ public CommandResponse Handle(UnmanageWindowCommand command)
// 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;
_bus.InvokeAsync(new SetNativeFocusCommand(focusTarget));
_containerService.HasPendingFocusSync = true;

_bus.Emit(new WindowUnmanagedEvent(window.Id, window.Handle));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public void Handle(WindowDestroyedEvent @event)
// If window is in tree, detach the removed window from its parent.
_bus.Invoke(new UnmanageWindowCommand(window));
_bus.Invoke(new RedrawContainersCommand());
_bus.Invoke(new SyncNativeFocusCommand());
}
}
}
13 changes: 9 additions & 4 deletions GlazeWM.Domain/Windows/EventHandlers/WindowFocusedHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
using GlazeWM.Domain.Containers.Commands;
using GlazeWM.Domain.Containers.Events;
using GlazeWM.Domain.Workspaces;
using GlazeWM.Domain.Workspaces.Commands;
using GlazeWM.Infrastructure.Bussing;
using GlazeWM.Infrastructure.Common.Events;
using Microsoft.Extensions.Logging;
using static GlazeWM.Infrastructure.WindowsApi.WindowsApiService;

namespace GlazeWM.Domain.Windows.EventHandlers
{
Expand Down Expand Up @@ -61,7 +59,7 @@ public void Handle(WindowFocusedEvent @event)
return;
}

_bus.InvokeAsync(new SetNativeFocusCommand(pendingFocusContainer));
_bus.InvokeAsync(new SyncNativeFocusCommand());
return;
}

Expand All @@ -71,8 +69,15 @@ public void Handle(WindowFocusedEvent @event)
if (window is null || window?.IsDisplayed == false)
return;

_logger.LogWindowEvent("Window focused", window);
_logger.LogWindowEvent("Native focus event", window);

var focusedContainer = _containerService.FocusedContainer;

// Focus is already set to the WM's focused container.
if (window == focusedContainer)
return;

// Update the WM's focus state.
_bus.Invoke(new SetFocusedDescendantCommand(window));
_bus.Emit(new FocusChangedEvent(window));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public void Handle(WindowHiddenEvent @event)
// Detach the hidden window from its parent.
_bus.Invoke(new UnmanageWindowCommand(window));
_bus.Invoke(new RedrawContainersCommand());
_bus.Invoke(new SyncNativeFocusCommand());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ public void Handle(WindowMinimizedEvent @event)

_bus.Invoke(new ReplaceContainerCommand(minimizedWindow, window.Parent, window.Index));

// Reassign focus to appropriate container.
_bus.InvokeAsync(new SetNativeFocusCommand(focusTarget));
// Focus should be reassigned to appropriate container.
_bus.Invoke(new SetFocusedDescendantCommand(focusTarget));
_containerService.HasPendingFocusSync = true;

_containerService.ContainersToRedraw.Add(workspace);
_bus.Invoke(new RedrawContainersCommand());
_bus.Invoke(new SyncNativeFocusCommand());
}
}
}
1 change: 1 addition & 0 deletions GlazeWM.Domain/Windows/EventHandlers/WindowShownHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public void Handle(WindowShownEvent @event)

_bus.Invoke(new ManageWindowCommand(@event.WindowHandle));
_bus.Invoke(new RedrawContainersCommand());
_bus.Invoke(new SyncNativeFocusCommand());
}
}
}
Loading

0 comments on commit 12fd5b2

Please sign in to comment.