Skip to content

feat(compile): embed comctl32 v6 manifest so Windows UI apps get themed controls - #4683

Merged
proggeramlug merged 2 commits into
mainfrom
win32-comctl-manifest-4681
Jun 7, 2026
Merged

feat(compile): embed comctl32 v6 manifest so Windows UI apps get themed controls#4683
proggeramlug merged 2 commits into
mainfrom
win32-comctl-manifest-4681

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

What

The biggest fix for the "controls look 40 years old" complaint in discussion #3486. Compiled Windows UI apps bound comctl32 v5 because Perry embedded no application manifest into the linked .exe, so every common control (buttons, list views, edit boxes…) rendered in the unthemed Win95/classic style regardless of the OS theme. This embeds a Win32 manifest declaring the Microsoft.Windows.Common-Controls v6 side-by-side dependency, which activates visual styles (the Fluent look) on Windows 10/11.

DWM (#4682) modernizes the frame; this modernizes the controls. Together they close most of the visual gap on the existing Win32 backend.

Changes

  • New windows_app.manifest — comctl32 v6 dependency + an asInvoker execution level (declaring it explicitly keeps UI binaries out of the UAC installer-detection heuristic).
  • Link step writes the manifest to a temp file and passes /MANIFEST:EMBED /MANIFESTUAC:NO /MANIFESTINPUT:<path>. Both link.exe and lld-link embed /MANIFESTINPUT: content via /MANIFEST:EMBED with no external mt.exe/rc.exe, so this works on the MSVC path and the cross-from-mac lld-link path alike. /MANIFESTUAC:NO suppresses the linker's auto-generated UAC fragment so it can't emit a second trustInfo block alongside ours.
  • Gated on ctx.needs_ui so console-only binaries stay manifest-free.
  • Hoisted the manifest to a pub(super) const WINDOWS_APP_MANIFEST and added regression tests guarding the v6 dependency, public key token, and asInvoker level.

Why not DPI in the manifest

This manifest deliberately does not declare dpiAware/dpiAwareness. Perry sets DPI awareness at runtime via dpi_compat (issue #303, to keep Win7 startup working and choose per-monitor-v2); a manifest declaration would conflict with that path. DPI stays out of scope here.

Testing

  • cargo test -p perry --bin perry windows_link_tests7 passed (5 existing subsystem guards + 2 new manifest guards).
  • cargo check -p perry clean; cargo fmt clean.
  • The runtime-if is_windows link branch compiles on the macOS host (not cfg-gated), so the embed code is type-checked here. The actual embedding + control theming needs a Windows runner / manual smoke before merge — confirm dumpbin /manifest (or Resource Hacker) shows the Common-Controls v6 dependency in a built UI .exe and that the ToDo sample renders themed controls.

Part of #4681, discussion #3486.

@proggeramlug

Copy link
Copy Markdown
Contributor Author

Lint fix + Windows smoke verification ✅

Lint failure was the 2000-line file-size gate — the manifest additions pushed link/mod.rs to 2038 lines. Fixed by extracting WINDOWS_APP_MANIFEST + the embed logic into a sibling link/windows_manifest.rs (embed_app_manifest(cmd, needs_ui)); mod.rs re-exports the const so windows_link_tests still reaches it. No behavior change. mod.rs is now 2000 lines and scripts/check_file_size.sh passes.

Verified on a real Windows build (host: Windows 11, lld-link, MSVC 14.50 toolchain) — this is the smoke the PR description flagged as still needed:

  • Built perry + perry-ui-windows release, compiled test-files/test_issue_442_inline_button_bg.ts (a perry/ui app).
  • mt.exe -inputresource:app.exe;#1 extracted the embedded manifest:
    <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls"
      version="6.0.0.0" processorArchitecture="*"
      publicKeyToken="6595b64144ccf1df" language="*"/>
    ...
    <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
    → Common-Controls v6, correct public key token, and a single trustInfo/asInvoker block (no duplicate — /MANIFESTUAC:NO did its job).
  • needs_ui gate confirmed: a console-only program (console.log) produced an exe with no resource section (mt.exe reports "did not contain a resource section") and ran correctly.

cargo test -p perry --bin perry windows_link_tests → 7 passed.

Ralph Küpper added 2 commits June 7, 2026 05:31
Compiled Windows UI apps bound comctl32 v5 because no application manifest
was embedded, so every common control (buttons, list views, edit boxes)
rendered in the unthemed Win95/classic style regardless of the OS theme —
the dated look reported in discussion #3486. Embed a Win32 manifest that
declares the Microsoft.Windows.Common-Controls v6 side-by-side dependency,
which activates visual styles (the Fluent look) on Windows 10/11.

- New windows_app.manifest: comctl32 v6 dependency + asInvoker execution
  level (keeps UI binaries out of the UAC installer-detection heuristic).
- Link step writes it to a temp file and passes /MANIFEST:EMBED
  /MANIFESTUAC:NO /MANIFESTINPUT: to the linker. Both link.exe and lld-link
  embed it with no external mt.exe/rc.exe. /MANIFESTUAC:NO avoids a second
  linker-generated trustInfo block alongside the one in our manifest.
- Gated on ctx.needs_ui so console-only binaries stay manifest-free.
- Hoisted the manifest to a pub(super) const + added regression tests
  guarding the v6 dependency and asInvoker level.

Part of #4681.
The comctl32 v6 manifest additions pushed link/mod.rs over the 2000-line CI
gate (scripts/check_file_size.sh) — the `lint` job failed with "File size limit
exceeded".

Move the Windows system-library link line and the manifest embed into a sibling
link/windows_link.rs module (add_system_libs + embed_app_manifest); mod.rs
re-exports WINDOWS_APP_MANIFEST so windows_link_tests still reaches it. mod.rs
drops to 1966 lines. No behavior change.

Verified on Windows (lld-link): a UI exe embeds the manifest with
Common-Controls v6 + a single asInvoker trustInfo block (mt.exe extract), while
a console-only exe gets no resource section — the needs_ui gate holds.
@proggeramlug
proggeramlug force-pushed the win32-comctl-manifest-4681 branch from 45b75ef to 822b48a Compare June 7, 2026 03:36
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main to clear the merge conflict in windows_link_tests.rs (main's windows_subsystem_needs_ui tests landed in the same region — kept both test sets).

Also note: main had since grown link/mod.rs by a few lines, so the extract-to-sibling refactor now also moves the Windows system-library link line into the new module (renamed link/windows_manifest.rslink/windows_link.rs, exposing add_system_libs + embed_app_manifest). mod.rs drops to 1966 lines — comfortable headroom under the 2000 gate instead of sitting right at it. Link args emitted are byte-identical; windows_link_tests11 passed (5 existing + 4 main's subsystem + 2 manifest).

@proggeramlug
proggeramlug merged commit a7e471e into main Jun 7, 2026
13 checks passed
@proggeramlug
proggeramlug deleted the win32-comctl-manifest-4681 branch June 7, 2026 03:51
proggeramlug added a commit that referenced this pull request Jun 8, 2026
)

Continues the #4681 Win32/GDI Fluent polish after #4682 (default DWM chrome) and #4683 (comctl32 v6 themed controls).

- Mica backdrop by default: apply_default_window_chrome now also requests DWMWA_SYSTEMBACKDROP_TYPE = DWMSBT_MAINWINDOW at window creation. Visible on the DWM-drawn title bar on Win11 22H2+, ignored (E_INVALIDARG) on older systems. Keeps the opaque client background, so no #1542 black-area regression; full client-area blur-through stays the app.setVibrancy opt-in.
- WM_DPICHANGED handling: the main window proc now honors the OS-suggested DPI-scaled rect via SetWindowPos and relayouts the root (mirrors WM_SIZE), so windows stay crisp when dragged across monitors with mixed scaling. DPI awareness was already opted into at startup.
- Refreshed the stale geisterhand_style.rs caveat: opacity/border/shadow apply paths are real now (#210/#230 closed).

Verified: perry-ui-windows builds, perry links a UI app that creates its window without crashing, and all 18 perry windows_link tests pass.

Co-authored-by: Ralph Kuepper <ralph@skelpo.com>
proggeramlug added a commit that referenced this pull request Jul 6, 2026
#6051)

* fix(link): #6023 LNK1158 'cannot run mt.exe' on Windows MSVC UI builds

MSVC link.exe implements /MANIFEST:EMBED by shelling out to the Windows
SDK's mt.exe. Perry launches a vswhere-located link.exe from a plain
shell (not a vcvars64.bat developer prompt), so the SDK bin dir is not
on PATH and every UI build since the comctl32-v6 manifest embed landed
(v0.5.1129 / #4683) died with LNK1158.

- When the linker is MSVC link.exe, probe for mt.exe: if it isn't on
  PATH already, locate the Windows SDK bin\<ver>\<arch> dir that holds
  it (registry KitsRoot10, ProgramFiles roots, legacy path — same probe
  order as find_msvc_lib_paths) and prepend it to the child's PATH.
- If mt.exe can't be found anywhere, skip the manifest embed with a
  loud warning instead of failing the link — an unthemed app that
  builds beats a fatal LNK1158 (mirrors the existing manifest-write-
  failure fallback). lld-link embeds manifests in-process and is
  untouched.
- Pass /IGNORE:4006: /FORCE:MULTIPLE makes the duplicate-definition
  merge deliberate, and the hundreds of LNK4006 lines it produced
  buried the real error in the report.

Closes #6023

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test/style: rustfmt fix + assert x64-over-x86 arch preference in mt.exe probe

cargo fmt wrapped the long fixture array (lint gate), and per CodeRabbit
review the newest-version fixture now carries both x64 and x86 mt.exe so
the asserted x64 pick actually exercises the arch preference instead of
just version ordering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Ralph <ralph@skelpo.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant