Skip to content

Fix taskbar becoming unresponsive on startup and display changes - #1126

Open
phatMT97 wants to merge 3 commits into
unchihugo:masterfrom
phatMT97:fix/taskbar-startup-unresponsive
Open

phatMT97 wants to merge 3 commits into
unchihugo:masterfrom
phatMT97:fix/taskbar-startup-unresponsive

Conversation

@phatMT97

@phatMT97 phatMT97 commented Sep 10, 2026 •

Copy link
Copy Markdown

Summary

Fixes an issue where the Windows taskbar becomes unresponsive / unclickable during startup or display topology changes (multi-monitor, resolution, DPI changes, waking from sleep) when the Taskbar Widget is enabled.

Related Issues

Motivation

Sometimes on Windows boot or after display/resolution changes, the taskbar stops responding to clicks (Start menu, taskbar icons, system tray cannot be clicked) until FluentFlyout is closed.

This happens because:

  1. TaskbarWindow is sized to the entire taskbar (containerWidth x containerHeight) and shown with SWP_SHOWWINDOW before the window region is computed and applied.
  2. If layout calculation or UI automation takes time or throws during startup, TaskbarWindow acts as an invisible overlay window swallowing all mouse clicks across the entire taskbar.
  3. TaskbarWindow lacked WM_NCHITTEST pass-through, so any unclipped area intercepted taskbar clicks.
  4. UpdateWindowRegion's on_error handler reset the region to IntPtr.Zero (removing the region and making the window cover the entire taskbar).
  5. MonitorUtil.GetSelectedMonitor and GetSelectedTaskbarHandle threw ArgumentException when monitors.Count == 0 during display initialization (Math.Clamp(x, 0, -1)).
  6. checkWindowClass used className.Equals("Shell_SecondaryTrayWnd") where className is a StringBuilder, resulting in reference comparison failure.

Type of Change

  • Bug fix

What Changed

  • Windows/TaskbarWindow.xaml.cs:
    • Handled WM_NCHITTEST in WindowProc: returns HTTRANSPARENT (-1) for coordinates outside the active interactive Widget and TaskbarVisualizer bounding boxes, letting Windows pass all mouse input through to the underlying taskbar (Shell_TrayWnd).
    • Reordered CalculateAndSetPosition: calculates and sanitizes widget/visualizer rects and applies UpdateWindowRegion before showing the window with SWP_SHOWWINDOW.
    • Hides TaskbarWindow (SWP_HIDEWINDOW) when there is no active widget/visualizer content.
    • Replaced SetWindowRgn(windowHandle, IntPtr.Zero, true) in on_error with an empty region (CreateRectRgn(0, 0, 0, 0)) so errors never cause full-taskbar coverage.
    • Added empty monitor list guard in GetSelectedTaskbarHandle.
    • Fixed StringBuilder.Equals in checkWindowClass using OrdinalIgnoreCase string comparison.
    • Added WM_DISPLAYCHANGE handling to re-resolve cached handles upon display topology changes.
  • Windows/TaskbarWindow.xaml:
    • Changed WidgetCanvas Background="Transparent" to Background="{x:Null}" to prevent empty canvas space from intercepting WPF hit tests.
  • Classes/Utils/MonitorUtil.cs:
    • Added empty monitor list check in GetSelectedMonitor before Math.Clamp.
  • Classes/NativeMethods.cs:
    • Added constants WM_NCHITTEST, HTTRANSPARENT, and HTCLIENT.

Checklist

  • Code changes are manually tested and working.
  • Formatting and naming are consistent with the project.
  • Self-review of changes is done.
  • AI tools were used (if yes, I reviewed and fully understand the changes myself).

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@github-actions github-actions Bot added the TaskbarWindow Changes to TaskbarWindow; the container for taskbar widgets label Sep 10, 2026
@phatMT97

Copy link
Copy Markdown
Author

I have read and agree to the FluentFlyout CLA

@phatMT97
phatMT97 force-pushed the fix/taskbar-startup-unresponsive branch from 779c7d6 to 424800a Compare September 10, 2026 08:24
github-actions Bot added a commit that referenced this pull request Sep 10, 2026
@phatMT97
phatMT97 force-pushed the fix/taskbar-startup-unresponsive branch from 424800a to 004653d Compare September 15, 2026 14:23
@github-actions github-actions Bot added the MainWindow / Media Flyout Changes to MainWindow including the Media Flyout label Sep 15, 2026
@phatMT97
phatMT97 force-pushed the fix/taskbar-startup-unresponsive branch from 004653d to 43ad6d1 Compare September 15, 2026 14:44
@phatMT97 phatMT97 mentioned this pull request Sep 16, 2026
6 of 9 tasks
- Add WM_NCHITTEST handler to pass through mouse events outside active widget/visualizer bounds to the underlying taskbar (HTTRANSPARENT)
- Calculate widget and visualizer layout regions before showing TaskbarWindow, preventing momentary or persistent full-taskbar hit-test blocking overlay
- Initialize TaskbarWindow with empty window region on source initialized and reset to empty region on closed
- Detect taskbar resolution/position/DPI geometry changes and invalidate stale automation element and tray handle caches
- Forward WM_DISPLAYCHANGE from MainWindow to TaskbarWindow and retry display refresh if monitor list is transiently empty during switch
- Clamp widget and visualizer coordinates strictly within valid taskbar boundaries and reject negative region coordinates
- Hide TaskbarWindow when no interactive content is active
- Prevent full-window region reset on region calculation failure
- Guard against empty monitor list during startup and display changes in GetSelectedTaskbarHandle and MonitorUtil (avoids Math.Clamp ArgumentException)
- Fix string comparison in checkWindowClass for secondary taskbar detection
- Set WidgetCanvas Background to null so empty canvas areas do not capture WPF mouse hit testing
@phatMT97
phatMT97 force-pushed the fix/taskbar-startup-unresponsive branch from 7b6d1d4 to 970119a Compare September 28, 2026 07:42
@phatMT97

Copy link
Copy Markdown
Author

Hi @unchihugo, I have rebased this branch onto the latest master and resolved the conflict with #1173 (keeping the HWND_TOP z-order intact alongside the hit-test region fixes).
All CI checks are passing green now. When you have a moment, please take a look. Thank you!

@unchihugo

Copy link
Copy Markdown
Owner

Hi @phatMT97, thanks for looking to contribute. I haven't noticed these issues myself when changing DPI, displays, etc.

Are there steps to reproduce this issue? Thanks!

@phatMT97

phatMT97 commented Oct 2, 2026

Copy link
Copy Markdown
Author

Hi @unchihugo,

Thanks for following up!

These issues are mostly caused by timing edge cases during startup and temporary OS states while the display configuration is changing. Since they depend on transient states, they can be difficult to reproduce consistently.

However, similar issues have been reported by multiple users in #701, #573, #892, #1130, and #597.

Here is some more detail on the cases covered by this PR:


1. Taskbar becomes unclickable / buttons are blocked during startup (Fixes #701, #391)

This can happen when TaskbarWindow is created before a valid widget position is available. For example, UI Automation may take a few seconds to find _trayElement during startup.

On master, if UpdateWindowRegion fails or receives an invalid rect, the exception handler calls:

SetWindowRgn(windowHandle, IntPtr.Zero, true);

Passing IntPtr.Zero removes the window's clipping region. Since the window still has its full allocated size (containerWidth x containerHeight), it can end up covering the entire taskbar.

At the same time, TaskbarWindow did not handle WM_NCHITTEST, and WidgetCanvas used Background="Transparent". This meant the invisible window could still receive mouse input, so clicks on Start, taskbar buttons, and the system tray could be blocked.

This PR fixes that in a few places:

  • WM_NCHITTEST now returns HTTRANSPARENT (-1) for points outside the active widget/visualizer rects, allowing input to pass through to Shell_TrayWnd.
  • The window starts with an empty region created by CreateRectRgn(0, 0, 0, 0).
  • If region creation fails, it falls back to an empty region instead of using IntPtr.Zero.
  • UpdateWindowRegion is applied before SetWindowPos(..., SWP_SHOWWINDOW), so the window is already clipped when it becomes visible.

2. Widget disappears or crashes when monitors are disconnected, switched with Win+P, or wake from sleep (Fixes #573, #892, #597)

The display configuration can be in a transient state during these operations. For a short period, Windows may report no monitors at all.

On master, MonitorUtil.cs currently does:

var monitors = Screen.AllScreens;
return monitors[Math.Clamp(targetMonitor, 0, monitors.Length - 1)];

When monitors.Length == 0, the upper bound becomes -1, so Math.Clamp throws:

System.ArgumentException: min (0) cannot be greater than max (-1)!

A similar case can happen in GetSelectedTaskbarHandle when taskbarHandles.Count == 0.

Because this exception occurs during the update cycle, the widget can stop updating and disappear.

This PR handles the empty state explicitly instead of assuming that at least one monitor or taskbar is always available.

WM_DISPLAYCHANGE is also forwarded from MainWindow so the cached handles can be invalidated and recalculated after Windows finishes updating the display topology.


3. Widget can keep stale UI Automation geometry after a DPI or resolution change

There is also a case where the HWND stays the same while its geometry changes.

On master, ResetTaskbarCachesIfHandleChanged(taskbarHandle) only invalidates the UI Automation cache when the taskbar HWND changes.

However, changing DPI or resolution does not necessarily recreate Shell_TrayWnd. The HWND can remain unchanged even though its size or position has changed.

As a result, _trayElement and _widgetElement can keep the bounding boxes from the previous display configuration, leaving the widget incorrectly positioned or clipped.

This PR replaces ResetTaskbarCachesIfHandleChanged with ResetTaskbarCachesIfGeometryChanged.

The cache is now invalidated when any of these change:

  • taskbar HWND
  • taskbar RECT
  • GetDpiForWindow() result

4. Secondary taskbars are not detected correctly with 3+ monitors

There is also a small issue in the secondary taskbar detection code in TaskbarWindow.xaml.cs:

StringBuilder className = new(256);
...
if (className.Equals("Shell_SecondaryTrayWnd"))

StringBuilder.Equals(object) does not compare the contents with the string value here, so the condition never matches Shell_SecondaryTrayWnd.

As a result, secondary taskbars can be missed on setups with three or more monitors.

The comparison is changed to use the actual string contents:

className.ToString().Equals(
    "Shell_SecondaryTrayWnd",
    StringComparison.OrdinalIgnoreCase)

Overall, these cases come from assuming that the Windows taskbar and display state is always valid and stable. In practice, Windows can temporarily report incomplete display information or leave UI Automation geometry out of date during startup and display transitions.

Please let me know if you need any further details or adjustments.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

MainWindow / Media Flyout Changes to MainWindow including the Media Flyout TaskbarWindow Changes to TaskbarWindow; the container for taskbar widgets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Add Title Here [BUG] Taskbar become unclickable [BUG] Taskbar Widget and Taskbar Visualizer

2 participants