Skip to content

Fix OverflowException in WindowChromeWorker._HandleNCHitTest when dragging across different-DPI monitors - #1997

Open
bobo198504 wants to merge 2 commits into
QL-Win:masterfrom
bobo198504:fix/window-drag-overflow-multimonitor
Open

Fix OverflowException in WindowChromeWorker._HandleNCHitTest when dragging across different-DPI monitors#1997
bobo198504 wants to merge 2 commits into
QL-Win:masterfrom
bobo198504:fix/window-drag-overflow-multimonitor

Conversation

@bobo198504

@bobo198504 bobo198504 commented Sep 6, 2026

Copy link
Copy Markdown

Problem

Dragging the preview window onto a monitor with a different DPI — e.g. a non-primary 4K display — crashed QuickLook with:

System.OverflowException: 算术运算导致溢出。
   at System.Windows.Shell.WindowChromeWorker._HandleNCHitTest(WM uMsg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at System.Windows.Shell.WindowChromeWorker._WndProc(...)
   at System.Windows.Interop.HwndSource.PublicHooksFilterMessage(...)

This is the crash reported in #1996 (crashes when dragging the preview window on a non-primary monitor). It happens on any preview type — the trigger is the shared ViewerWindow, not a specific plugin.

Root cause

The overflow is raised by WPF (.NET Framework 4.6.2) WindowChromeWorker._HandleNCHitTest on the WM_NCHITTEST message. I decompiled the installed PresentationFramework.dll and confirmed that _HitTestNca and DpiHelper.DevicePixelsToLogical/DeviceRectToLogical contain no overflowable (int) casts — the bad arithmetic lives entirely inside WPF's per-window DPI math, which becomes inconsistent as the window is dragged onto a monitor with a different DPI. That inconsistency can't be corrected from the outside, so the safe fix is to avoid the offending hit-test entirely.

Changes

  • ViewerWindow: answer WM_NCHITTEST with HTCLIENT to bypass the overflowing WindowChrome hit-test, and restore window dragging manually (WM_NCLBUTTONDOWN + HT CAPTION) on the title area, since Window.DragMove() depends on a hit-test result.
  • ViewerWindow: apply the WM_DPICHANGED suggested rect so the HWND and WPF geometry stay in sync when the window moves between monitors.
  • App / SHCore: opt into Per-Monitor V2 DPI awareness (SetProcessDpiAwarenessContext) with a V1 fallback.
  • WindowHelper / ViewerWindow.Actions: clamp MoveWindow coordinates/size and sanitize non-finite window sizes so a degenerate rect can never reach Win32/WPF.

Fixes #1996.

Summary by Sourcery

Prevent cross-monitor DPI changes from triggering WPF hit-test overflows in preview windows.

Bug Fixes:

  • Prevent preview windows from crashing with an OverflowException when dragged across monitors with different DPI settings.
  • Keep window geometry synchronized during DPI transitions and preserve manual title-bar dragging and resizing behavior.
  • Guard window positioning and sizing against invalid, non-finite, or out-of-range values.

Enhancements:

  • Adopt Per-Monitor V2 DPI awareness with a Per-Monitor V1 fallback for supported Windows versions.

…gging across different-DPI monitors

Dragging the preview window onto a monitor with a different DPI (e.g. a
non-primary 4K display) crashed with an arithmetic overflow inside WPF's
WindowChromeWorker hit-test. The crash was triggered on WM_NCHITTEST, whose
DPI math can't be made safe from the outside, so:

- Short-circuit WM_NCHITTEST to HTCLIENT in ViewerWindow, and restore window
  dragging by hand (WM_NCLBUTTONDOWN + HT CAPTION) on the title area.
- Apply the WM_DPICHANGED suggested rect so the HWND and WPF geometry stay in
  sync as the window moves between monitors.
- Opt into Per-Monitor V2 DPI awareness (SetProcessDpiAwarenessContext) with a
  V1 fallback.
- Clamp MoveWindow coordinates/size and sanitize non-finite window size so a
  degenerate rect can never reach Win32/WPF.

Fixes QL-Win#1996
@sourcery-ai

sourcery-ai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR prevents the .NET Framework 4.6.2 WindowChrome WM_NCHITTEST overflow during cross-DPI monitor dragging by bypassing WPF hit-testing, restoring native title-bar dragging, synchronizing WM_DPICHANGED geometry, enabling Per-Monitor V2 DPI awareness with fallback, and sanitizing all window coordinates and sizes.

Sequence diagram for cross-DPI viewer window dragging

sequenceDiagram
    actor User
    participant ViewerWindow
    participant WPF as WindowChromeWorker
    participant Win32
    participant Monitor

    User->>ViewerWindow: TitleArea_MouseLeftButtonDown
    ViewerWindow->>Win32: ReleaseCapture()
    ViewerWindow->>Win32: SendMessage(WM_NCLBUTTONDOWN, HTCAPTION)
    User->>Monitor: Drag window across DPI boundary
    Monitor-->>ViewerWindow: WM_DPICHANGED(suggestedRect)
    ViewerWindow->>Win32: MoveWindow(suggestedRect)
    User->>ViewerWindow: Window message WM_NCHITTEST
    ViewerWindow-->>WPF: HTCLIENT
    Note over ViewerWindow,WPF: WPF WindowChrome hit-testing is bypassed
Loading

Flow diagram for safe viewer window geometry

flowchart TD
    Input["Requested window size and position"] --> Size["PositionWindow"]
    Size --> Finite["FinitePositive"]
    Finite --> Move["MoveWindow"]
    Move --> Clamp["ToInt32Clamped"]
    Clamp --> Rect["Clamped coordinates and minimum dimensions"]
    Rect --> Win32["User32.MoveWindow"]
Loading

File-Level Changes

Change Details Files
Bypass the .NET Framework WindowChrome non-client hit-test path and reimplement title-bar dragging through native messages.
  • Return HTCLIENT for WM_NCHITTEST to prevent the overflowing WPF hit-test.
  • Start dragging with WM_NCLBUTTONDOWN/HTCAPTION from the title area.
  • Skip manual dragging for borderless windows.
QuickLook/ViewerWindow.xaml.cs
Synchronize window geometry with per-monitor DPI transitions and enable Per-Monitor V2 awareness.
  • Apply the WM_DPICHANGED suggested rectangle.
  • Use SetProcessDpiAwarenessContext with a Windows 10 V2 capability check and fall back to SetProcessDpiAwareness.
  • Add the DPI-awareness context interop declaration.
QuickLook/ViewerWindow.xaml.cs
QuickLook/App.xaml.cs
QuickLook/NativeMethods/SHCore.cs
Prevent invalid or oversized geometry from reaching Win32 and WPF arithmetic.
  • Clamp pixel coordinates and dimensions to Int32-safe values with minimum physical window sizes.
  • Clamp transformed pixel coordinates.
  • Replace NaN, infinity, and non-positive requested sizes with valid fallbacks.
QuickLook.Common/Helpers/WindowHelper.cs
QuickLook/ViewerWindow.Actions.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#1996 Prevent QuickLook from crashing with an OverflowException when dragging a video preview window across monitors with different DPI, particularly onto a non-primary 4K display.
#1996 Ensure the preview window remains draggable and its geometry stays synchronized when moved between monitors with different DPI settings.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="QuickLook/ViewerWindow.xaml.cs" line_range="222-226" />
<code_context>
+        // overflow is in WPF's own DPI math, so no condition on the window rect can reliably
+        // detect it). Dragging still works because the content panels use WM_NCLBUTTONDOWN(HT
+        // CAPTION) directly rather than relying on a hit test result.
+        if (msg == WM_NCHITTEST)
+        {
+            handled = true;
+            return new IntPtr(1); // HTCLIENT
+        }
+
+        if (msg == WM_DPICHANGED && lParam != IntPtr.Zero)
</code_context>
<issue_to_address>
**issue (bug_risk):** Returning HTCLIENT for every WM_NCHITTEST prevents WindowChrome from reporting resize hit-test zones such as HTLEFT, HTRIGHT, HTTOP, and HTBOTTOM. Because ViewerWindow remains configured with ResizeMode="CanResize", users can no longer resize the preview window through its borders.

**Triggers:** When the user attempts to resize the preview window from any border or corner.

**Suggested fix:** Handle only the problematic hit-test path, or implement the WindowChrome resize hit-test zones and initiate native resizing manually instead of returning HTCLIENT for every point.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: QuickLook/ViewerWindow.xaml.cs:226


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread QuickLook/ViewerWindow.xaml.cs
… WM_NCHITTEST

Sourcery correctly flagged that returning HTCLIENT for every WM_NCHITTEST
disabled the resize borders while ResizeMode=CanResize. Report HTLEFT/HTRIGHT/
HTTOP/HTBOTTOM and the corner zones at the window edges, and HTCLIENT elsewhere,
still bypassing the WindowChrome hit-test that overflows during cross-DPI drags.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

crashes when dragging video preview window on non-primary monitor

1 participant