Skip to content

Commit

Permalink
Force double SetWindowPos for BarWindow (#886)
Browse files Browse the repository at this point in the history
It seems this is necessary for windows in monitors with 100% scaling. This will need further investigation in #887 for the difference in behavior between C# and what's fetched from the XAML.
  • Loading branch information
dalyIsaac authored Apr 30, 2024
1 parent 2062e05 commit 19cf769
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 4 deletions.
2 changes: 1 addition & 1 deletion docs/docs/customize/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ The `bar:height` key is a special key that is used to communicate to Whim the de
Notes:

- The corresponding style must not contain any property other than `Height`.
- This setting is overwritten if `Height` is explicitly set in <xref:Whim.Bar.BarConfig>.
- This setting is overwritten if `Height` is explicitly set in <xref:Whim.Bar.BarConfig> - this may be required in some monitor configurations - tracked in [#887](https://github.com/dalyIsaac/Whim/issues/887)
- The actual height of the bar may differ from the specified one due to overflowing elements.

> [!NOTE]
Expand Down
2 changes: 1 addition & 1 deletion src/Whim.Bar/BarPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private void ShowAll()
foreach (BarWindow barWindow in _monitorBarMap.Values)
{
barWindow.UpdateRect();
deferPosHandle.DeferWindowPos(barWindow.WindowState);
deferPosHandle.DeferWindowPos(barWindow.WindowState, forceTwoPasses: true);
_context.NativeManager.SetWindowCorners(
barWindow.WindowState.Window.Handle,
DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DONOTROUND
Expand Down
37 changes: 37 additions & 0 deletions src/Whim.Tests/Native/DeferWindowPosHandleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,43 @@ WindowPosState windowPosState2
AssertSetWindowPos(internalCtx, windowPosState2, expectedCallCount: numPasses);
}

[Theory, AutoSubstituteData<DeferWindowPosHandleCustomization>]
internal void Dispose_ForceTwoPasses(
IContext ctx,
IInternalContext internalCtx,
WindowPosState windowPosState1,
WindowPosState windowPosState2
)
{
// Given multiple windows, and a monitor which has a scale factor == 100
int monitorScaleFactor = 100;
int numPasses = 2;

using DeferWindowPosHandle handle = new(ctx, internalCtx);
internalCtx.DeferWindowPosManager.CanDoLayout().Returns(true);

IMonitor monitor1 = Substitute.For<IMonitor>();
monitor1.ScaleFactor.Returns(100);
IMonitor monitor2 = Substitute.For<IMonitor>();
monitor2.ScaleFactor.Returns(monitorScaleFactor);

ctx.MonitorManager.GetEnumerator().Returns((_) => new List<IMonitor>() { monitor1, monitor2 }.GetEnumerator());

// When disposing
handle.DeferWindowPos(windowPosState1.WindowState, windowPosState1.HwndInsertAfter, windowPosState1.Flags);
handle.DeferWindowPos(
windowPosState2.WindowState,
windowPosState2.HwndInsertAfter,
windowPosState2.Flags,
forceTwoPasses: true
);
handle.Dispose();

// Then the windows are laid out twice
AssertSetWindowPos(internalCtx, windowPosState1, expectedCallCount: numPasses);
AssertSetWindowPos(internalCtx, windowPosState2, expectedCallCount: numPasses);
}

[Theory, AutoSubstituteData<DeferWindowPosHandleCustomization>]
internal void Dispose_NoWindowOffset(IContext ctx, IInternalContext internalCtx, WindowPosState windowPosState)
{
Expand Down
18 changes: 16 additions & 2 deletions src/Whim/Native/DeferWindowPosHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public sealed class DeferWindowPosHandle : IDisposable
private readonly List<WindowPosState> _windowStates = new();
private readonly List<WindowPosState> _minimizedWindowStates = new();

private bool forceTwoPasses;

/// <summary>
/// The default flags to use when setting the window position.
/// </summary>
Expand Down Expand Up @@ -80,13 +82,25 @@ IEnumerable<WindowPosState> windowStates
/// The flags to use when setting the window position. This overrides the default flags Whim sets,
/// except when the window is maximized or minimized.
/// </param>
/// <param name="forceTwoPasses">
/// Window scaling can be finicky. Whim will set a window's position twice for windows in monitors
/// with non-100% scaling. Regardless of this, some windows need this even for windows with
/// 100% scaling.
/// </param>
public void DeferWindowPos(
IWindowState windowState,
HWND? hwndInsertAfter = null,
SET_WINDOW_POS_FLAGS? flags = null
SET_WINDOW_POS_FLAGS? flags = null,
bool forceTwoPasses = false
)
{
Logger.Debug($"Adding window {windowState.Window} after {hwndInsertAfter} with flags {flags}");

if (forceTwoPasses)
{
this.forceTwoPasses = true;
}

// We use HWND_BOTTOM, as modifying the Z-order of a window
// may cause EVENT_SYSTEM_FOREGROUND to be set, which in turn
// causes the relevant window to be focused, when the user hasn't
Expand Down Expand Up @@ -136,7 +150,7 @@ public void Dispose()
int numPasses = 1;
foreach (IMonitor monitor in _context.MonitorManager)
{
if (monitor.ScaleFactor != 100)
if (monitor.ScaleFactor != 100 || forceTwoPasses)
{
numPasses = 2;
break;
Expand Down

0 comments on commit 19cf769

Please sign in to comment.