Skip to content

[windows] Restore Windows Snap for custom title bar#3069

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:codex/windows-window-snap
May 24, 2026
Merged

[windows] Restore Windows Snap for custom title bar#3069
ten9876 merged 1 commit into
aethersdr:mainfrom
jensenpat:codex/windows-window-snap

Conversation

@jensenpat

Copy link
Copy Markdown
Collaborator

Summary

  • Preserve native Windows overlapped-window styles for the main window so Snap Assist can recognize it as a normal resizable window.
  • On Windows, avoid Qt 6.8.3's Qt::FramelessWindowHint/WS_POPUP path for the main window while frameless mode is enabled; instead hide the native frame with WM_NCCALCSIZE.
  • Return Windows non-client hit-test values (HTCAPTION, resize edges/corners) for the custom title bar and window edges so the center title-bar handle invokes the OS move/snap UI.
  • Keep existing frameless propagation for dialogs/floating child windows unchanged.

Why

Qt 6.8.3 creates Qt::FramelessWindowHint top-level windows as WS_POPUP and its Windows platform hit-test path skips frameless windows. That prevents Windows from taking over the drag operation for Snap Assist even when the app returns HTCAPTION. Keeping native frame style bits and hiding the frame through WM_NCCALCSIZE matches the Windows custom-frame contract and restores the native snap preview.

Testing

  • Built on Windows/MSVC with 8-way parallel build:
    powershell -NoProfile -ExecutionPolicy Bypass -Command ". 'C:\Users\patj\Documents\AetherSDR\scripts\enter-msvc.ps1' -Arch x64; cmake --build build-msvc --config RelWithDebInfo --parallel 8"
  • Ran windeployqt after build:
    C:\Users\patj\Documents\AetherSDR\Qt\6.8.3\msvc2022_64\bin\windeployqt.exe --release --compiler-runtime C:\Users\patj\Documents\AetherSDR\upstream\AetherSDR-windows-window-snap\build-msvc\AetherSDR.exe
  • Manual Windows verification: dragging the center handle in the frameless title bar now shows the Windows Snap UI and snaps to top/left/right.

windeployqt emitted the existing local warnings about missing dxcompiler.dll/dxil.dll and unset VCINSTALLDIR; deployment otherwise completed.

@jensenpat jensenpat changed the title Restore Windows Snap for custom title bar [windows] Restore Windows Snap for custom title bar May 24, 2026
@jensenpat jensenpat marked this pull request as ready for review May 24, 2026 22:35
@jensenpat jensenpat requested a review from a team as a code owner May 24, 2026 22:35

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for this @jensenpat — nice, focused fix. The approach (preserving native overlapped-window style bits and hiding the frame via WM_NCCALCSIZE instead of leaning on Qt::FramelessWindowHint) is the standard Windows custom-frame contract and is the right way to get Snap Assist back. AppSettings usage matches our conventions, scope is appropriate (4 files, all Windows-frame related), and the non-Windows behavior is untouched.

A few observations / questions — none blocking, just want to make sure they're intentional:

1. setFramelessWindow(bool on) ignores on on Windowssrc/gui/MainWindow.cpp:12895

The Windows branch always calls applyWindowsCustomFrame() regardless of on. Tracing it through:

  • The on parameter only controls what gets persisted to AppSettings("FramelessWindow") at the top of the function.
  • applyWindowsCustomFrame() triggers SWP_FRAMECHANGED, which fires WM_NCCALCSIZE, and nativeEvent then re-reads the (just-saved) setting via mainWindowCustomFrameEnabled() to decide whether to swallow the message.

So toggling on=false on Windows does end up restoring native chrome via the redraw path, which is correct — but it's pretty non-obvious. A one-line comment in the Windows branch explaining that applyWindowsCustomFrame() works for both directions because nativeEvent gates on the persisted setting would save the next reader a lot of head-scratching.

2. m_sizeGrip visibility when on=true on Windowssrc/gui/MainWindow.cpp:12916

With the new Windows path, edge/corner hits from WM_NCHITTEST already give native resize, so the bottom-right QSizeGrip is redundant (and may double up visually with the snap-aware resize edges). Worth confirming this isn't visually awkward, or hiding the size grip unconditionally on Windows when custom-frame is active.

3. applyWindowsCustomFrame() ORs style bits but never removes WS_POPUPsrc/gui/MainWindow.cpp (the new function)

desiredStyle = style | WS_CAPTION | WS_THICKFRAME | .... If style happens to still have WS_POPUP set (e.g. from a stale flag state), this preserves it alongside WS_CAPTION, which Win32 treats as conflicting. With the constructor change to skip Qt::FramelessWindowHint on Windows this shouldn't happen in practice, but explicitly masking off WS_POPUP (desiredStyle = (style & ~WS_POPUP) | WS_OVERLAPPEDWINDOW) would make it robust against future changes that might re-introduce the flag.

4. QCursor::pos() vs the WM_NCHITTEST lParamsrc/gui/MainWindow.cpp (in nativeEvent)

You correctly use nativePos (from GET_X_LPARAM/GET_Y_LPARAM) for the resize-edge math because it's in physical pixels, then switch to QCursor::pos() for the isSystemMoveAreaAt check because that helper operates in Qt logical coordinates. Just confirming this is intentional — fine in practice, but worth a one-line comment so a future reader doesn't "fix" the inconsistency.

Otherwise looks good. The TitleBar::isSystemMoveAreaAt helper is a clean separation, and routing the title-bar drag through WM_NCLBUTTONDOWN / HTCAPTION (instead of startSystemMove) is the right call given the Qt 6.8.3 hit-test skip.


🤖 aethersdr-agent · cost: $4.7125 · model: claude-opus-4-7

@ten9876 ten9876 merged commit cee00a6 into aethersdr:main May 24, 2026
5 checks passed
@jensenpat jensenpat linked an issue May 26, 2026 that may be closed by this pull request
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
## Summary

- Preserve native Windows overlapped-window styles for the main window
so Snap Assist can recognize it as a normal resizable window.
- On Windows, avoid Qt 6.8.3's `Qt::FramelessWindowHint`/`WS_POPUP` path
for the main window while frameless mode is enabled; instead hide the
native frame with `WM_NCCALCSIZE`.
- Return Windows non-client hit-test values (`HTCAPTION`, resize
edges/corners) for the custom title bar and window edges so the center
title-bar handle invokes the OS move/snap UI.
- Keep existing frameless propagation for dialogs/floating child windows
unchanged.

## Why

Qt 6.8.3 creates `Qt::FramelessWindowHint` top-level windows as
`WS_POPUP` and its Windows platform hit-test path skips frameless
windows. That prevents Windows from taking over the drag operation for
Snap Assist even when the app returns `HTCAPTION`. Keeping native frame
style bits and hiding the frame through `WM_NCCALCSIZE` matches the
Windows custom-frame contract and restores the native snap preview.

## Testing

- Built on Windows/MSVC with 8-way parallel build:
`powershell -NoProfile -ExecutionPolicy Bypass -Command ".
'C:\Users\patj\Documents\AetherSDR\scripts\enter-msvc.ps1' -Arch x64;
cmake --build build-msvc --config RelWithDebInfo --parallel 8"`
- Ran `windeployqt` after build:

`C:\Users\patj\Documents\AetherSDR\Qt\6.8.3\msvc2022_64\bin\windeployqt.exe
--release --compiler-runtime
C:\Users\patj\Documents\AetherSDR\upstream\AetherSDR-windows-window-snap\build-msvc\AetherSDR.exe`
- Manual Windows verification: dragging the center handle in the
frameless title bar now shows the Windows Snap UI and snaps to
top/left/right.

`windeployqt` emitted the existing local warnings about missing
`dxcompiler.dll`/`dxil.dll` and unset `VCINSTALLDIR`; deployment
otherwise completed.
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.

Windows Aero Snap (drag-to-top maximize) does not work on Windows

2 participants