microsoft / microsoft/microsoft-ui-xaml

Minimizing a window with open popups result in "ghost" HWNDs that block pointer events once the window is restored

Open
#11,068 6 comments 2 reactions 0 assignees View on GitHub
area-Popup area-Windowing bug team-CompInput
Dominant language
C++
Stars
8.4k
Forks
942
Avg merge
2d 7h
Merged PRs (30d)
105

Description

### Describe the bug

If a window that owns WinUI popups (e.g. context menus, flyouts, etc) is minimized while the popup is open, the "PopupHost" hwnds become "ghost" windows once the minimized window is restored. These "ghost" windows are WS_VISIBLE but do not render any content. The "ghost" windows block pointer input from going to underlying content.

The "ghost" windows only go away once the associated popup is re-opened. If a popup was a nested flyout, the nested flyout needs to be re-opened as well.

See the attached video in "actual behavior" below.

### Why is this important?

Any application that includes "minimize" functionality within a context menu will experience unexplainable pointer input issues. This issue is particularly hard to debug with no obvious workaround.

This bug also happens if the window is minimized from outside the application (e.g. with the "Windows Key + Down" shortcut) while the menu is open. So, it even impacts applications that don't have minimize functionality from a context menu like this POC demo does.

### Steps to reproduce the bug

Create a new blank WinUI project.

In `MainWindow.xaml` use the following code:

```xaml





















```

In `MainWindow.xaml.cs` use the following code:

```cs
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using WinRT.Interop;

namespace MinimizePopupBug
{
public sealed partial class MainWindow : Window
{
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

private const int SW_MINIMIZE = 6;

private static readonly SolidColorBrush GreenBrush = new(Colors.Green);
private static readonly SolidColorBrush RedBrush = new(Colors.Red);

public MainWindow()
{
InitializeComponent();
}

private void RootGrid_PointerEntered(object sender, PointerRoutedEventArgs e)
{
RootGrid.Background = GreenBrush;
StatusText.Text = "Receiving pointer events!";
}

private void RootGrid_PointerExited(object sender, PointerRoutedEventArgs e)
{
RootGrid.Background = RedBrush;
StatusText.Text = "No pointer found";
}

private void Minimize_Click(object sender, RoutedEventArgs e)
{
var hWnd = WindowNative.GetWindowHandle(this);
ShowWindow(hWnd, SW_MINIMIZE);
}

private void PaintOwnedWindows_Click(object sender, RoutedEventArgs e)
{
var hWnd = WindowNative.GetWindowHandle(this);
var ownedWindows = new List();

EnumThreadWindows(GetCurrentThreadId(), (hChild, _) =>
{
if (hChild != hWnd && GetWindow(hChild, GW_OWNER) == hWnd)
{
ownedWindows.Add(hChild);
}
return true;
}, IntPtr.Zero);

foreach (var owned in ownedWindows)
{
int backdropType = DWMSBT_MAINWINDOW; // Mica
DwmSetWindowAttribute(owned, DWMWA_SYSTEMBACKDROP_TYPE,
ref backdropType, sizeof(int));
}
}

private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(uint dwThreadId, EnumWindowsProc lpEnumFunc, IntPtr lParam);

[DllImport("kernel32.dll")]
private static extern uint GetCurrentThreadId();

[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);

[DllImport("dwmapi.dll")]
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int dwAttribute,
ref int pvAttribute, int cbAttribute);

private const uint GW_OWNER = 4;
private const int DWMWA_SYSTEMBACKDROP_TYPE = 38;
private const int DWMSBT_MAINWINDOW = 2; // Mica
}
}
```

Run the application, open the menu, and click minimize. Restore the window from taskbar.

### Actual behavior

After restoring, the areas where popups were previously rendered do not allow pointer events to go to any underlying window. This is because the "PopupHost" top-level windows that render the popups are WS_VISIBLE while WinUI does not render anything. All pointer events get consumed by this window's input source.

This video shows the behavior:

https://github.com/user-attachments/assets/88a895ef-fe36-4d1f-9752-17302e273511

Things to note:
1. When the window preview was shown (via hovering the window in the taskbar), the context menus were still rendered
2. The "Apply mica to owned windows" button gives each "PopupHost" HWND a mica backdrop. This lets us visualize where the input-blocking "ghost" windows are rendered.
3. The ghost windows disappear after re-opening then closing each individual popup
4. The "ghost windows" block input from _any_ window beneath it, including windows other than the original minimized window.

### Expected behavior

The popups should be closed when the window becomes minimized. When the window is restored, the popups should remain closed and their PopupHost HWNDs should not be WS_VISIBLE.

### Screenshots

_No response_

### NuGet package version

1.8.251003001

### Windows version

_No response_

### Additional context

_No response_

Contributor guide

Open the contributing guide

Research direction

Reproduce the issue in a blank WinUI project using MainWindow.xaml and MainWindow.xaml.cs, opening the menu before minimizing and restoring the window. Inspect the PopupHost HWNDs during minimize and restore. Done means the popups are closed, their hosts are not visible, and pointer events reach the restored window and content beneath it.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, csharp
Domain
desktop, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.