Skip to content

fix(gui): menus stay open and click-to-tune works with Frameless Window on (#6272). Principle VIII. - #6328

Merged
ten9876 merged 2 commits into
aethersdr:mainfrom
chibondking:fix/6272-frameless-hittest
Oct 9, 2026
Merged

ten9876 merged 2 commits into
aethersdr:mainfrom
chibondking:fix/6272-frameless-hittest

Conversation

@chibondking

Copy link
Copy Markdown
Collaborator

Fixes #6272.

What

On Windows with Frameless Window on, the main window's ☰ menu and the panadapter's right-click menu flashed open and closed immediately, and double-click to tune did nothing. All three work with Frameless off. This regressed with the unified title bar (#6198) and is still on main after #6269.

Root cause

With Frameless on, the main window uses Qt's expanded client area with CustomizeWindowHint and no title or caption-button hints (WindowChrome::chromeFlags). For exactly those flags, Qt 6.12 answers WM_NCHITTEST in the else if (CustomizeWindowHint) branch of QWindowsWindow::handleNonClientHitTest (qwindowswindow.cpp:3415-3427):

  • It reads the live mouse buttons with GetAsyncKeyState.
  • Whenever they differ from the last hit test (oldMouseButtonState, a function-static shared by every window), it synthesizes a mouse press or release with QWindowSystemInterface::handleMouseEvent<SynchronousDelivery> at the cursor, anywhere in the window.

Windows sends WM_NCHITTEST around a real click, so the click arrives twice:

  • The ☰ menu opens on the first press (popup() on pressed), the second press lands outside it and dismisses it, and the replay guard stops it reopening.
  • The panadapter's exec() context menu is closed the same way.
  • The extra press/release pairs break double-click detection, so double-click to tune never fires.

This also explains two puzzles from the reproduction on the issue:

  • The agent automation bridge's own click verbs never reproduced it: they deliver Qt events directly and never cause a WM_NCHITTEST.
  • One earlier real-click run passed: the shared static makes the outcome depend on hit-test timing.

How

QWindowsContext runs the window's own nativeEvent() before its hit-test switch (qwindowscontext.cpp:858 vs :1019). Under the expanded client area, MainWindow::nativeEvent() now answers WM_NCHITTEST itself, with no synthetic events:

  • the resize codes (HTLEFT … HTBOTTOMRIGHT) on a band of SM_CXSIZEFRAME + SM_CXPADDEDBORDER at the window edges, the same band Qt used;
  • no band while maximized or fullscreen;
  • HTCLIENT everywhere else.

WindowChrome::expandedFrameHit() is the pure part. window-chrome.md now says edge resize on this path rests on this handler. It applies on Windows 10 and 11 alike, since the Qt branch doesn't depend on the OS version.

Popped-out panadapters use plain Qt::FramelessWindowHint, which Qt hit-tests on its ordinary path. They aren't affected and aren't changed (proven below).

+114 / −6 in 4 files; every behaviour change is inside #ifdef Q_OS_WIN.

Testing

Proof with the agent automation bridge, using real OS input

Run on Windows 10 22H2 (Intel HD 520), FLEX-6600 connected, AetherSDR UI scale 85%, your real settings with Frameless Window on. Builds came from the fork's Windows Installer workflow (Qt 6.12):

  • Before: main e1785e78, run 37902283037.
  • After: this branch 1c51a580, run 37902771818.

Method:

  • Clicks are SendInput at positions from dump_tree, with physical = Qt logical × 0.85.
  • Popup detection: the process's visible Qt…PopupSaveBits windows, 300 ms after each click.
  • Tune detection: the slice frequency read back through the bridge after each double-click.
  • The popped-out pan was made with pan float and targeted by its window title. Its click points sit below the slice flag; my first popped-out run was invalid because the click landed on the flag.
Target Action Before (main) After (this PR)
Main window ☰ menu stays open 0 / 5 5 / 5
Main window Panadapter right-click menu stays open 0 / 5 5 / 5
Main window Double-click tunes the slice 0 / 5 5 / 5
Popped-out pan Right-click menu stays open 5 / 5 5 / 5
Popped-out pan Double-click tunes the slice 5 / 5 5 / 5

Edge resize still works on the fix build. Real SendInput drags, with the window rect from GetWindowRect:

Edge WM_NCHITTEST there Drag Window before → after
Bottom 15 (HTBOTTOM) down 30 1240×451 → 1240×481
Bottom 15 up 30 1240×481 → 1240×451
Left 10 (HTLEFT) left 30 327 x, 1210 w → 297 x, 1240 w
Left 10 right 30 297 x, 1240 w → 327 x, 1210 w

Three earlier drags didn't resize because the press landed on another window (the floating pan, then the floating TX Controls). WindowFromPoint confirmed that, and those drags are excluded.

Unit test (local, Qt 6.12.0 pinned, MSVC 2022, offscreen)

The new expandedFrameHit checks in unified_title_bar_test all pass:

  • client area at the ☰ corner, mid-window, and one pixel past the band;
  • every edge and corner;
  • no band while maximized;
  • a point outside the window.

Mutation-checked:

  • An off-by-one on the band fails "the first pixel past the band is client area".
  • An always-client answer fails every edge and corner check.

One existing check in the same binary, "four radios fit whole in an 1800 px bar", fails in my local Windows offscreen run with and without this change (also on plain main). CI on main is green.

Not verified

Agent automation bridge: gaps found

  • Click verbs that go through Windows. The bridge's clickAt, rightClick and doubleClick deliver Qt events and skip WM_NCHITTEST, so they can't reproduce input bugs below Qt. A real-input mode (SendInput at a widget's centre) would let the bridge prove this class of bug on its own.
  • Non-ASCII targets. The MCP wrapper garbles non-ASCII target names (Theme Editor…); plain tooltips are a workaround.

🤖 Generated with Claude Code

…ow on (aethersdr#6272). Principle VIII.

With Frameless Window on, the main window uses Qt's expanded client
area with CustomizeWindowHint and no title or caption-button hints.
Qt 6.12 answers WM_NCHITTEST for exactly those flags
(qwindowswindow.cpp:3415-3427) by reading the live button state with
GetAsyncKeyState. Whenever that state differs from the last hit test
(a function-static shared by every window), it synthesizes a mouse
press or release with handleMouseEvent(SynchronousDelivery) at the
cursor, anywhere in the window. Windows sends WM_NCHITTEST around a
real click, so the click arrives twice. The hamburger menu opens on the
first press and closes on the second, the panadapter's exec() context
menu closes the same way, and the extra press/release pairs break
double-click to tune. The bridge's own click verbs never cause a
WM_NCHITTEST, so they could not reproduce it. The outcome also depends
on hit-test timing, which is why one earlier real-click run passed.

MainWindow::nativeEvent() runs before Qt's hit test (qwindowscontext.cpp
:858 vs :1019) and now answers WM_NCHITTEST itself under the expanded
client area. It returns the resize codes on a band of SM_CXSIZEFRAME +
SM_CXPADDEDBORDER at the window edges (none while maximized or
fullscreen), the same band Qt used, and HTCLIENT everywhere else, with
no synthetic events. WindowChrome::expandedFrameHit() is the pure part,
covered in unified_title_bar_test; an off-by-one in the band and an
always-client answer each fail it.

Popped-out panadapters use plain FramelessWindowHint, which Qt
hit-tests on its ordinary path; they are unaffected and unchanged.
window-chrome.md now says edge resize on this path rests on this
handler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chibondking
chibondking marked this pull request as ready for review October 9, 2026 09:25
@chibondking
chibondking requested a review from a team as a code owner October 9, 2026 09:25

@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.

Reviewed the head checkout at 1c51a580, not just the diff. All five checks are green, including check-windows — the lane that actually compiles the new #ifdef Q_OS_WIN block.

1. Issue fit — Yes

#6272 reports three symptoms with Frameless Window on: the ☰ menu flashes, the panadapter context menu flashes, and click-to-tune doesn't fire. The before/after bridge table covers all three with real SendInput input (0/5 → 5/5 each), which is exactly the evidence the maintainer asked for in the second issue comment ("the bridge's own click verbs don't reproduce this"). The mechanism — answering WM_NCHITTEST in MainWindow::nativeEvent() before QWindowsContext reaches its own hit-test switch — is sound independently of whether the GetAsyncKeyState/shared-static narrative is exactly right.

Not claimed and correctly out of scope: the issue thread's side note about the window growing ~7 px per Frameless toggle. That is the native-client-rect geometry path (#6303), untouched here.

2. Scope

File Change Claimed? Verdict
src/gui/MainWindow.cpp frameHitCode() + the WM_NCHITTEST branch in nativeEvent(); the old WM_NCCALCSIZE comment reworded yes In scope
src/gui/WindowChrome.h FrameHit enum + pure expandedFrameHit(); <QPoint> include yes In scope
tests/unified_title_bar_test.cpp 10 expandedFrameHit checks; <QPoint>/<QRect> includes yes In scope
docs/architecture/window-chrome.md Windows bullet rewritten for the new handler yes In scope

Everything in the diff is explained by the issue. No new public/protocol surface (FrameHit/expandedFrameHit are internal to the GUI). No CHANGELOG.md entry — correct. No removed guard: the only deleted lines are the two comment lines the new comment replaces, and the deleted doc sentence ("recheck edge resize after a Qt bump") is obsoleted rather than lost, since the handler is ours now.

No sibling left behind. MainWindow is the only window that reaches WindowChrome::configure() (MainWindow.cpp:1140, :10143), and it is the only holder of either Qt::ExpandedClientAreaHint or Qt::CustomizeWindowHint in the tree — I grepped both, since the PR's root cause names the CustomizeWindowHint branch, which could in principle have snared unrelated dialogs. It doesn't: nothing else sets that flag.

Security audit (required by the harness). Ran; it refuted all 36 CodeGuard findings and found nothing independently. Every category resolves to C++/Qt idiom or pre-existing test fixtures: && read as a shell AND, markdown backticks in comments, // ── box-drawing read as homoglyphs, the window-role parameter read as a prompt role override, and RFC 5737 192.0.2.x display strings in untouched radio-switcher fixtures at tests/unified_title_bar_test.cpp:431. The CS-VLN-PATH-PROC/CG-PATH-001 lines don't correspond to any path handling — the three files contain no QFile/QDir/QStandardPaths at all. Nothing to carry forward.

3. Blockers

None.

4. Nits (all non-blocking)

  • src/gui/MainWindow.cpp:3946-3948 — the vertical band uses the horizontal metric.

                border = GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi)
                    + GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi);

    One scalar serves all four edges, so the top/bottom band is SM_CXSIZEFRAME-wide. The sibling WM_NCCALCSIZE block 15 lines below deliberately distinguishes them (borderX vs borderY from SM_CXSIZEFRAME/SM_CYSIZEFRAME). They're equal on every stock Windows theme, so this is cosmetic rather than wrong — but the inconsistency between two adjacent blocks will read as an oversight later. If you'd rather keep expandedFrameHit() single-scalar, a one-line comment saying the two metrics are equal in practice would settle it.

  • docs/architecture/window-chrome.md:92-99 — #6224 now lands in our handler. The bullet above still says the Snap Layouts flyout is missing because "nothing returns HTMAXBUTTON on hover", and the PR's "Not verified" says that is unchanged — both true. What changed is where a future fix goes: before this PR it was a Qt-side gap, now it has to be a FrameHit::MaxButton case in expandedFrameHit() plus the caption-button rect. Worth one sentence in the doc so #6224 isn't re-diagnosed as a Qt limitation.

  • Top 8 px of the title bar is a resize zone. expandedFrameHit() applies the band at the top as well, and TitleBar's layout has a zero top margin (src/gui/TitleBar.cpp:231, setContentsMargins(16, 0, 16, 0)), so the topmost ~8 physical px of ☰ and of the caption buttons answer HTTOP/HTTOPRIGHT rather than HTCLIENT. The 5/5 ☰ result shows the centre is fine, and Qt's own band did the same, so this is pre-existing rather than introduced. But note the Linux fallback deliberately reserves TitleBar::kHeight from the top band for precisely this reason (#4886, FramelessResizer::install(this, 6, TitleBar::kHeight)), and Windows now has an equivalent knob for the first time. Your call whether to match; it is not a regression either way.

  • Window move and double-click-maximize aren't in the proof matrix. The handler answers HTCLIENT across the whole caption region, so anything that depended on a caption hit code there is gone. Reading the tree, this should be fine: TitleBar::startWindowMove() (src/gui/TitleBar.cpp:850-886) drags via QWindow::startSystemMove() from a Qt mouse press, and mouseDoubleClickEvent → handleTitleDoubleClick toggles maximize the same way — neither routes through WM_NCHITTEST. Since the resize table covers only bottom and left drags, one real-SendInput drag on a bare title-bar gap (window position changes) and one real double-click (window maximizes) would close the gap; failing that, listing them under "Not verified" is enough.

5. What I tried to break

  • A sibling window with the same defect. Grepped ExpandedClientAreaHint, NoTitleBarBackgroundHint and CustomizeWindowHint across src/ and tests/: MainWindow is the only holder of any of them, and the only caller of WindowChrome::configure(). The PR's claim that popped-out panadapters are unaffected holds for a second reason beyond the 5/5 control rows — they never carry the flag the Qt branch keys on.
  • Window dragging and double-click-maximize as collateral of blanket HTCLIENT. This was my leading blocker candidate; it died on reading TitleBar::startWindowMove() and handleTitleDoubleClick(), both of which work from Qt-level mouse events on the client area. Reasoned from code, not run — hence the nit above.
  • The boundary arithmetic in expandedFrameHit(), against GetWindowRect's exclusive right/bottom vs QRect's inclusive right()/bottom(). The conversion at MainWindow.cpp:3952-3953 is QRect(left, top, right-left, bottom-top), so rect.right() == window.right - 1, the last valid column — and point.x() > window.right() - border catches it. The test vectors agree (win(100,50,1000,600) → right() 1099, bottom() 649, both asserted as edges). No off-by-one, no gap, no overlap: 108 is the first client column with border == 8.
  • Win32 failure paths. GetWindowRect's return is unchecked, but the zero-initialised RECT window{} yields an empty QRect whose contains() is false → FrameHit::Client → HTCLIENT. GetDpiForWindow returning 0 makes both metrics 0 → border <= 0 → HTCLIENT. Both degrade to the safe answer rather than a wrong resize code. GET_X_LPARAM/GET_Y_LPARAM (not LOWORD) sign-extend, which is required since WM_NCHITTEST screen coordinates go negative on a left-hand monitor — the classic bug here, and it isn't present.
  • Maximized and fullscreen. IsZoomed || isFullScreen() zeroes the band, so a maximized window answers HTCLIENT across the inset region the WM_NCCALCSIZE branch carves out. Consistent with the gate in that branch, and matches the doc.
  • The tests, for proving-the-implementation-back-to-itself. They test the new pure function, so by construction they can't fail against unfixed code — but they are not tautological: the band width, the corner precedence and the one-pixel-past-the-band row would each fail against a plausible wrong implementation, and the PR records the two mutations that kill them. What they cannot reach is the part that actually fixes #6272: that nativeEvent() claims the message before Qt, and that IsZoomed/isFullScreen() zero the band. There is no socket-free seam for a WM_NCHITTEST dispatch, so per AGENTS.md's test-layer boundary this is correctly bridge/real-input evidence rather than CTest, and the PR supplies it. unified_title_bar_test is in no ci.yml -R filter, so CI's green does not include these checks.
  • What I could not test. The root-cause narrative — Qt's else if (CustomizeWindowHint) branch at qwindowswindow.cpp:3415-3427 reading GetAsyncKeyState and synthesising presses off a function-static oldMouseButtonState. There is no Qt source tree in this environment, so I am taking that on the PR's word; a read of those lines would settle it. It doesn't change the verdict: the before/after table is the load-bearing evidence, and the fix is correct whatever the precise shape of Qt's answer, as long as ours doesn't synthesise events — which it demonstrably doesn't. No socket tests added or removed. Every finding above is reasoned from code; I have no build and ran nothing.

6. Recommendation

Approve with nits. The change is narrow, platform-gated, root-caused rather than papered over, and backed by the real-OS-input proof the issue thread explicitly required — including control rows showing the popped-out pan path is untouched and that edge resize survives. The four nits are all documentation or disclosure, none of them merge-gating. Concrete next step: add the title-bar drag and double-click-maximize rows to the proof (or to "Not verified"), and consider the one-sentence #6224 pointer in window-chrome.md. Thanks for the unusually clear root-cause write-up and for the bridge-gap notes at the end — the "real-input mode" request is worth its own issue.


🤖 aethersdr-agent · cost: $10.4070 · model: claude-opus-5

@aethersdr-agent

Copy link
Copy Markdown
Contributor

Cisco CodeGuard — static analysis of this PR (41 finding(s))

  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:9636
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:9748
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:9870
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:9871
  • [MEDIUM] CG-PATH-001 — Potential path traversal in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:9872
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:6168
  • [HIGH] CS-VLN-PATH-PROC — Vulnerability: CS-VLN-PATH-PROC in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:6247
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:523
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:553
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:556
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:578
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:3639
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:1277
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:1801
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:1893
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:1970
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:2012
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:2047
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:2326
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:3581
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:3672
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:6110
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:7916
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:7981
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:8055
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:8140
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:8321
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:8524
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:9737
  • [LOW] CS-PII-IP — PII detected: ip_address in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:6860
  • [CRITICAL] CS-INJ-role_override — Prompt injection: role override attempt in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:11695
  • [CRITICAL] CS-INJ-role_override — Prompt injection: role override attempt (detected after NFKC normalization) in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:11695
  • [HIGH] CS-INJ-homoglyph — Mixed Unicode scripts detected (possible homoglyph attack) in src/gui/MainWindow.cpp /tmp/aetherclaude/pr-6328/src/gui/MainWindow.cpp:0
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in src/gui/WindowChrome.h /tmp/aetherclaude/pr-6328/src/gui/WindowChrome.h:30
  • [HIGH] CS-VLN-SSRF-PRIV-192 — Vulnerability: CS-VLN-SSRF-PRIV-192 in tests/unified_title_bar_test.cpp /tmp/aetherclaude/pr-6328/tests/unified_title_bar_test.cpp:431
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in tests/unified_title_bar_test.cpp /tmp/aetherclaude/pr-6328/tests/unified_title_bar_test.cpp:96
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in tests/unified_title_bar_test.cpp /tmp/aetherclaude/pr-6328/tests/unified_title_bar_test.cpp:642
  • [CRITICAL] CS-VLN-CMDI-AMP — Vulnerability: CS-VLN-CMDI-AMP in tests/unified_title_bar_test.cpp /tmp/aetherclaude/pr-6328/tests/unified_title_bar_test.cpp:660
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in tests/unified_title_bar_test.cpp /tmp/aetherclaude/pr-6328/tests/unified_title_bar_test.cpp:13
  • [CRITICAL] CS-VLN-CMDI-BACKTICK — Vulnerability: CS-VLN-CMDI-BACKTICK in tests/unified_title_bar_test.cpp /tmp/aetherclaude/pr-6328/tests/unified_title_bar_test.cpp:28

Automated static scan by Cisco DefenseClaw CodeGuard on the changed files. Advisory — some may be false positives; the review above verifies them.


🤖 aethersdr-agent · cost: $10.5668 · model: claude-opus-5

…or the top and bottom band (aethersdr#6272). Principle VIII.

Review follow-up. expandedFrameHit() took one band width for all four
edges, built from SM_CXSIZEFRAME, while the WM_NCCALCSIZE block beside
it uses SM_CXSIZEFRAME for left/right and SM_CYSIZEFRAME for top/bottom.
It now takes borderX and borderY, and nativeEvent() passes
SM_CXSIZEFRAME / SM_CYSIZEFRAME (+ SM_CXPADDEDBORDER) to match. The two
metrics are equal on stock Windows, so behaviour there is unchanged.

unified_title_bar_test adds a check with unequal bands. Making the top
band read borderX fails it.

window-chrome.md now says a Snap Layouts fix (aethersdr#6224) belongs in this
handler (HTMAXBUTTON over the bar's maximize button), not in Qt, and
that the top band covers the top few pixels of the bar, as Qt's did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chibondking

chibondking commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

@AetherClaude Thanks for the review. All four nits are answered below, at head 21cae89f.

1. Vertical band from the horizontal metric: fixed in 21cae89f.

  • expandedFrameHit() now takes borderX and borderY.
  • nativeEvent() passes SM_CXSIZEFRAME / SM_CYSIZEFRAME (+ SM_CXPADDEDBORDER), matching the WM_NCCALCSIZE block below it.
  • A new unified_title_bar_test check uses unequal bands (8 horizontal, 4 vertical). Making the top band read borderX fails it, and it passes as written. The run was local with Qt 6.12.0, offscreen; the only other failure in that binary is the known local-only "four radios fit…" check, which also fails on main.

2. Snap Layouts pointer: fixed in 21cae89f. window-chrome.md now says a #6224 fix belongs in this handler (HTMAXBUTTON over the bar's maximize button, from expandedFrameHit), not in Qt.

3. Top band over the title bar: kept as is, deliberately. It matches what Qt's own hit test did before this PR, so it's not a regression. Matching Linux (#4886, no top band under the bar) would remove top-edge resizing on Windows, which is a behaviour change better decided on its own than folded into this fix. window-chrome.md now states that the top band covers the top few pixels of the bar.

4. Title-bar move and double-click-maximize: proven on the 21cae89f build (fork Windows Installer run 37936659517, Qt 6.12). Run on Windows 10 22H2, 85% UI scale, Frameless on (window rect = client rect, zero frame). Real SendInput on the bar's empty drag area (titleBarDragGutter, 22 physical px below the top edge, clear of the resize band), with GetWindowRect / IsZoomed read before and after:

Action Before After
Drag +60, +40 158,63 1210×451 218,103 1210×451 (moved exactly)
Drag −60, −40 218,103 1210×451 158,63 1210×451
Double-click 158,63 1210×451, normal −8,−8 1382×784, maximized
Double-click again maximized 158,63 1210×451, normal

I also re-ran the issue's trials on the same build to make sure the follow-up commit changed nothing: ☰ menu 5/5, panadapter right-click menu 5/5, double-click tune 5/5.

🤖 Generated with Claude Code

@chibondking chibondking self-assigned this Oct 9, 2026
@ten9876 ten9876 self-assigned this Oct 9, 2026

@ten9876 ten9876 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving at 21cae89fe. There were no outstanding findings: no review threads, no blocking reviews, and the bot's four nits are answered in 21cae89f:

  • separate X/Y bands;
  • the #6224 pointer in window-chrome.md;
  • the top band kept deliberately and documented;
  • title-bar drag and double-click-maximize proven with real SendInput.

Verified here (Linux, Qt 6.12, offscreen):

  • unified_title_bar_test passes all checks, including the new expandedFrameHit cases.
  • Making the top band read borderX fails "the left/right band and the top/bottom band follow their own widths".
  • AetherSDR builds, and required CI is green, including check-windows, which compiles the #ifdef Q_OS_WIN handler.

Not verified here: the Windows runtime behaviour itself. I'm relying on the author's real-input before/after proof on Windows 10 (☰ menu, panadapter context menu and double-click tune went 0/5 → 5/5; edge resize, drag and maximize still work).

@ten9876
ten9876 merged commit 1ae4394 into aethersdr:main Oct 9, 2026
5 checks passed
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 10, Frameless Window on: panadapter click-to-tune, right-click menu and ☰ menu flash and close immediately (since #6198)

2 participants