Skip to content

Commit

Permalink
Switched back to DeferWindowPos (#127)
Browse files Browse the repository at this point in the history
## Removed

- `Win32Helper.SetWindowPos`

## Changed

- Replaced usage of `Win32Helper.SetWindowPos` with `WindowDeferPosHandle` or `WindowDeferPosHandle.SetWindowPos`
- Fixed debug values in `Win32Helper`
  • Loading branch information
dalyIsaac authored Jul 8, 2022
1 parent c30cb78 commit 0262579
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 87 deletions.
16 changes: 13 additions & 3 deletions src/Whim.Bar/BarPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ public void PostInitialize()
foreach (IMonitor monitor in _configContext.MonitorManager)
{
BarWindow barWindow = new(_configContext, _barConfig, monitor);
barWindow.Render();
_monitorBarMap.Add(monitor, barWindow);
}

ShowAll();
}

private void MonitorManager_MonitorsChanged(object? sender, MonitorsChangedEventArgs e)
Expand All @@ -61,10 +62,19 @@ private void MonitorManager_MonitorsChanged(object? sender, MonitorsChangedEvent
_monitorBarMap.Add(monitor, barWindow);
}

// Show all windows
ShowAll();
}

/// <summary>
/// Show all the bar windows.
/// </summary>
private void ShowAll()
{
using WindowDeferPosHandle deferPosHandle = new(_monitorBarMap.Count);

foreach (BarWindow barWindow in _monitorBarMap.Values)
{
barWindow.Render();
deferPosHandle.DeferWindowPos(barWindow.WindowState);
}
}

Expand Down
21 changes: 9 additions & 12 deletions src/Whim.Bar/BarWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ public sealed partial class BarWindow : Microsoft.UI.Xaml.Window
private readonly IConfigContext _configContext;
private readonly BarConfig _barConfig;
private readonly IMonitor _monitor;
private readonly IWindowState _windowLocation;

/// <summary>
/// The current window state.
/// </summary>
public IWindowState WindowState { get; }

/// <summary>
/// Creates a new bar window.
Expand Down Expand Up @@ -38,16 +42,16 @@ public BarWindow(IConfigContext configContext, BarConfig barConfig, IMonitor mon
int topMargin = (int)_barConfig.Margin.Top;
int bottomMargin = (int)_barConfig.Margin.Bottom;

_windowLocation = new WindowState(window, new Location(
WindowState = new WindowState(window, new Location(
x: _monitor.X + leftMargin,
y: _monitor.Y + rightMargin,
width: _monitor.Width - (leftMargin + rightMargin),
height: _barConfig.Height), WindowSize.Normal);

// Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/3689
Title = "Whim Bar";
Win32Helper.HideCaptionButtons(_windowLocation.Window.Handle);
Win32Helper.SetWindowCorners(_windowLocation.Window.Handle);
Win32Helper.HideCaptionButtons(WindowState.Window.Handle);
Win32Helper.SetWindowCorners(WindowState.Window.Handle);
this.SetIsShownInSwitchers(false);

// Set up the bar.
Expand All @@ -56,12 +60,5 @@ public BarWindow(IConfigContext configContext, BarConfig barConfig, IMonitor mon
RightPanel.Children.AddRange(_barConfig.RightComponents.Select(c => c(_configContext, _monitor, this)));
}

/// <summary>
/// Renders the bar in the correct location. Use this instead of Show() to ensure
/// the bar is rendered in the correct location.
/// </summary>
public void Render()
{
Win32Helper.SetWindowPos(_windowLocation);
}

}
2 changes: 1 addition & 1 deletion src/Whim.CommandPalette/CommandPaletteWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public void Activate(IEnumerable<(ICommand, IKeybind?)>? items = null, IMonitor?

base.Activate();
TextEntry.Focus(FocusState.Programmatic);
Win32Helper.SetWindowPos(
WindowDeferPosHandle.SetWindowPos(
new WindowState(_window, windowLocation, WindowSize.Normal),
_window.Handle
);
Expand Down
3 changes: 2 additions & 1 deletion src/Whim.FocusIndicator/FocusIndicatorPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,16 @@ private void WindowManager_WindowUnregistered(object? sender, WindowEventArgs e)

private void WindowManager_WindowUpdated(object? sender, WindowUpdateEventArgs e)
{
Logger.Verbose($"Window {e.Window} updated with update type {e.UpdateType}");
switch (e.UpdateType)
{
case WindowUpdateType.Uncloaked:
case WindowUpdateType.Cloaked:
case WindowUpdateType.MoveStart:
case WindowUpdateType.MinimizeStart:
case WindowUpdateType.Move:
Hide();
break;
case WindowUpdateType.Uncloaked:
case WindowUpdateType.Foreground:
case WindowUpdateType.MoveEnd:
case WindowUpdateType.MinimizeEnd:
Expand Down
2 changes: 1 addition & 1 deletion src/Whim.FocusIndicator/FocusIndicatorWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void Activate(IWindowState windowLocation)
width: focusedWindowLocation.Width + (borderSize * 2)
);

Win32Helper.SetWindowPos(
WindowDeferPosHandle.SetWindowPos(
new WindowState(_window, borderLocation, WindowSize.Normal),
windowLocation.Window.Handle
);
Expand Down
74 changes: 7 additions & 67 deletions src/Whim/Native/Win32Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static class Win32Helper
/// <param name="hwnd"></param>
public static void QuitApplication(HWND hwnd)
{
Logger.Debug($"Quitting application with HWND {hwnd}");
Logger.Debug($"Quitting application with HWND {hwnd.Value}");
PInvoke.SendNotifyMessage(hwnd, PInvoke.WM_SYSCOMMAND, new WPARAM(PInvoke.SC_CLOSE), 0);
}

Expand All @@ -31,7 +31,7 @@ public static void QuitApplication(HWND hwnd)
/// <param name="hwnd"></param>
public static void ForceForegroundWindow(HWND hwnd)
{
Logger.Debug($"Forcing window HWND {hwnd} to foreground");
Logger.Debug($"Forcing window HWND {hwnd.Value} to foreground");
// Implementation courtesy of https://github.com/workspacer/workspacer/commit/1c02613cea485f1ae97f70d6399f7124aeb31297
// keybd_event synthesizes a keystroke - see https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-keybd_event
PInvoke.keybd_event(0, 0, 0, 0);
Expand All @@ -44,7 +44,7 @@ public static void ForceForegroundWindow(HWND hwnd)
/// <param name="hwnd"></param>
public static bool HideWindow(HWND hwnd)
{
Logger.Debug($"Hiding window HWND {hwnd}");
Logger.Debug($"Hiding window HWND {hwnd.Value}");
return (bool)PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_HIDE);
}

Expand All @@ -53,7 +53,7 @@ public static bool HideWindow(HWND hwnd)
/// </summary>
public static bool ShowWindowMaximized(HWND hwnd)
{
Logger.Debug($"Showing window HWND {hwnd} maximized");
Logger.Debug($"Showing window HWND {hwnd.Value} maximized");
return (bool)PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED);
}

Expand All @@ -62,7 +62,7 @@ public static bool ShowWindowMaximized(HWND hwnd)
/// </summary>
public static bool ShowWindowMinimized(HWND hwnd)
{
Logger.Debug($"Showing window HWND {hwnd} minimized");
Logger.Debug($"Showing window HWND {hwnd.Value} minimized");
return (bool)PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_SHOWMINIMIZED);
}

Expand All @@ -71,7 +71,7 @@ public static bool ShowWindowMinimized(HWND hwnd)
/// </summary>
public static bool MinimizeWindow(HWND hwnd)
{
Logger.Debug($"Minimizing window HWND {hwnd}");
Logger.Debug($"Minimizing window HWND {hwnd.Value}");
return (bool)PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_MINIMIZE);
}

Expand All @@ -80,7 +80,7 @@ public static bool MinimizeWindow(HWND hwnd)
/// </summary>
public static bool ShowWindowNoActivate(HWND hwnd)
{
Logger.Debug($"Showing window HWND {hwnd} no activate");
Logger.Debug($"Showing window HWND {hwnd.Value} no activate");
return (bool)PInvoke.ShowWindow(hwnd, SHOW_WINDOW_CMD.SW_SHOWNOACTIVATE);
}

Expand Down Expand Up @@ -279,66 +279,6 @@ public static bool IsCloakedWindow(HWND hwnd)
}
}

/// <summary>
/// Using the given <paramref name="windowState"/>, sets the window's position.
/// </summary>
/// <param name="windowState"></param>
/// <param name="hwndInsertAfter">The window handle to insert show the given window behind.</param>
public static void SetWindowPos(IWindowState windowState, HWND? hwndInsertAfter = null)
{
// 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
// actually changed the focus.
hwndInsertAfter ??= (HWND)1; // HWND_BOTTOM

IWindow window = windowState.Window;

ILocation<int> offset = GetWindowOffset(window.Handle);
ILocation<int> location = Location.Add(windowState.Location, offset);

WindowSize windowSize = windowState.WindowSize;

SET_WINDOW_POS_FLAGS flags = SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED
| SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE
| SET_WINDOW_POS_FLAGS.SWP_NOCOPYBITS
| SET_WINDOW_POS_FLAGS.SWP_NOZORDER
| SET_WINDOW_POS_FLAGS.SWP_NOOWNERZORDER;

if (windowSize == WindowSize.Maximized || windowSize == WindowSize.Minimized)
{
flags = flags | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE;
}

PInvoke.SetWindowPos(
window.Handle,
(HWND)hwndInsertAfter,
location.X,
location.Y,
location.Width,
location.Height,
flags);

if (windowSize == WindowSize.Maximized)
{
if (!window.IsMinimized)
{
MinimizeWindow(window.Handle);
}
}
else if (windowSize == WindowSize.Minimized)
{
if (!window.IsMaximized)
{
ShowWindowMaximized(window.Handle);
}
}
else if (window.WindowClass != "Windows.UI.Core.CoreWindow")
{
ShowWindowNoActivate(window.Handle);
}
}

/// <summary>
/// Returns the window's offset.<br/>
/// This is based on the issue raised at https://github.com/workspacer/workspacer/issues/139,
Expand Down
127 changes: 127 additions & 0 deletions src/Whim/Native/WindowDeferPosHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;

namespace Whim;

/// <summary>
/// Sets the position of multiple windows at once, using <see cref="PInvoke.DeferWindowPos"/>.
/// As stated in Raymond Chen's blog post (https://devblogs.microsoft.com/oldnewthing/20050706-26/?p=35023),
/// this reduces the amount of repainting.
/// </summary>
public sealed class WindowDeferPosHandle : IDisposable
{
private nint _hWinPosInfo;
private readonly List<IWindow> _toMinimize;
private readonly List<IWindow> _toMaximize;
private readonly List<IWindow> _toNormal;

/// <summary>
/// Create a new <see cref="WindowDeferPosHandle"/> for <paramref name="count"/> windows.
/// This is to be used when setting the position of multiple windows at once.
///
/// <see cref="WindowDeferPosHandle"/> must be used in conjunction with a <c>using</c> block
/// or statement, otherwise <see cref="PInvoke.EndDeferWindowPos"/> won't be called.
/// </summary>
/// <param name="count">The number of windows to layout.</param>
public WindowDeferPosHandle(int count)
{
_hWinPosInfo = PInvoke.BeginDeferWindowPos(count);

_toMinimize = new List<IWindow>();
_toMaximize = new List<IWindow>();
_toNormal = new List<IWindow>();
}

/// <inheritdoc/>
public void Dispose()
{
foreach (IWindow w in _toMinimize)
{
if (!w.IsMinimized)
{
Win32Helper.MinimizeWindow(w.Handle);
}
}

foreach (IWindow w in _toMaximize)
{
if (!w.IsMaximized)
{
Win32Helper.ShowWindowMaximized(w.Handle);
}
}

foreach (IWindow w in _toNormal)
{
Win32Helper.ShowWindowNoActivate(w.Handle);
}

PInvoke.EndDeferWindowPos(_hWinPosInfo);
}

/// <summary>
/// Using the given <paramref name="windowState"/>, sets the window's position.
/// </summary>
/// <param name="windowState"></param>
/// <param name="hwndInsertAfter">The window handle to insert show the given window behind.</param>
public void DeferWindowPos(IWindowState windowState, HWND? hwndInsertAfter = null)
{
// 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
// actually changed the focus.
hwndInsertAfter ??= (HWND)1; // HWND_BOTTOM

IWindow window = windowState.Window;

ILocation<int> offset = Win32Helper.GetWindowOffset(window.Handle);
ILocation<int> location = Location.Add(windowState.Location, offset);

WindowSize windowSize = windowState.WindowSize;

SET_WINDOW_POS_FLAGS flags = SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED
| SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE
| SET_WINDOW_POS_FLAGS.SWP_NOCOPYBITS
| SET_WINDOW_POS_FLAGS.SWP_NOZORDER
| SET_WINDOW_POS_FLAGS.SWP_NOOWNERZORDER;

if (windowSize == WindowSize.Maximized)
{
_toMaximize.Add(window);
flags = flags | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE;
}
else if (windowSize == WindowSize.Minimized)
{
_toMinimize.Add(window);
flags = flags | SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE;
}
else
{
_toNormal.Add(window);
}

_hWinPosInfo = PInvoke.DeferWindowPos(
_hWinPosInfo,
window.Handle,
(HWND)hwndInsertAfter,
location.X,
location.Y,
location.Width,
location.Height,
flags);
}

/// <summary>
/// Set the position of a single window.
/// </summary>
/// <param name="windowState"></param>
/// <param name="hwndInsertAfter">The window handle to insert show the given window behind.</param>
public static void SetWindowPos(IWindowState windowState, HWND? hwndInsertAfter = null)
{
using WindowDeferPosHandle handle = new(1);
handle.DeferWindowPos(windowState, hwndInsertAfter);
}
}
4 changes: 4 additions & 0 deletions src/Whim/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ DwmGetWindowAttribute
DwmSetWindowAttribute
DWM_WINDOW_CORNER_PREFERENCE

BeginDeferWindowPos
DeferWindowPos
EndDeferWindowPos

GetAncestor
IsWindowVisible
GetWindowLong
Expand Down
2 changes: 1 addition & 1 deletion src/Whim/Window/WindowManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ private void WindowsEventHook(HWINEVENTHOOK hWinEventHook, uint eventType, HWND
// Try get the window
if (!_windows.TryGetValue(hwnd, out IWindow? window) || window == null)
{
Logger.Verbose($"Window {hwnd.Value} is not registered");
Logger.Verbose($"Window {hwnd.Value} is not registered, event type {eventType}");
window = RegisterWindow(hwnd);
if (window == null)
{
Expand Down
Loading

0 comments on commit 0262579

Please sign in to comment.