Add rounded corners mod for snapped/maximized windows - #5022
Add rounded corners mod for snapped/maximized windows#5022leshaalexey wants to merge 13 commits into
Conversation
This mod enables rounded corners for snapped and maximized windows in DWM, mimicking macOS behavior. It includes settings for corner style, radius, and debug logging.
|
Thanks for the pull request! This repository uses a two-stage review: an AI review that you run yourself, followed by a human review. To get started, comment See the pull request review process for the full details. |
|
/ai-review |
|
/ready-for-reviewer |
|
@leshaalexey |
|
/ready-for-reviewer |
|
@leshaalexey |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The idea is nice and the hook points are the right ones, but the symbol-resolution architecture and one system-wide side effect need to change before this can be merged. 1. void Wh_ModSettingsChanged() {
LoadSettings();
SystemParametersInfoW(SPI_SETDRAGFULLWINDOWS, TRUE, nullptr, SPIF_SENDCHANGE);
}
On top of that, Please remove the call. If you need DWM to re-evaluate existing windows after a settings change, find a scoped way to do it; corners will in any case update the next time each window's visuals are refreshed. 2. Substantial overlap with
The maintainer's consistent preference is to extend an existing mod with an option rather than merge a near-overlapping new mod. "Round maximized and snapped windows" fits naturally as a setting in 3. Drop the The README's premise ("
The current design also has a concrete user-visible failure mode. Finally, WindhawkUtils::SYMBOL_HOOK udwmDllHooks[] = {
{
{LR"(private: enum CORNER_STYLE __cdecl CTopLevelWindow::GetEffectiveCornerStyle(void))"},
&GetEffectiveCornerStyle_orig,
GetEffectiveCornerStyle_hook,
},
{
{LR"(private: float __cdecl CTopLevelWindow::GetRadiusFromCornerStyle(void))"},
&GetRadiusFromCornerStyle_orig,
GetRadiusFromCornerStyle_hook,
},
// ...
};
if (!WindhawkUtils::HookSymbols(udwm, udwmDllHooks, ARRAYSIZE(udwmDllHooks))) { ... }This also matters for Concretely: delete 4. HMODULE hUDWM = GetModuleHandleW(L"uDWM.dll");
if (!hUDWM) {
hUDWM = LoadLibraryW(L"uDWM.dll");
}In Just do what the other DWM mods do — 5. UINT dpi = GetDpiForSystem();
if (!dpi) dpi = 96;
return g_settings.radius * (dpi / 96.0f);
Per the call tree documented in 6. Remove the developer-only settings
7. Add a screenshot to the README This is a purely visual mod and the README has no image. A before/after screenshot (or a short GIF of snapping a window) makes a big difference both in the Windhawk mod browser and in review. Only Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ai-review |
Added authorship section to mod readme.
|
/ai-review |
|
@leshaalexey |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The revision is a big improvement — the hand-rolled RVA cache, the developer-only settings, the custom logging and the 1. This is a strong candidate for an option in Custom Window Corner Radius rather than a separate mod. You raised this yourself, so here's the reasoning from the catalog side. The two mods run in the same process, hook the same Note also that the README's compatibility warning is a symptom of the split, not an independent fact: Windhawk chains hooks, and the two mods are actually complementary (Custom Window Corner Radius replaces non-zero radii, yours replaces zero radii), so a user who wants both a custom radius and rounded maximized corners currently has no supported way to get it. As one mod with a "Keep corners rounded when maximized or snapped" option, that combination just works, and there's one place to maintain the udwm symbol list as builds change. Development for that mod happens at https://github.com/m417z/my-windhawk-mods. 2. README issues.
Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
Updated mod authorship section to indicate AI assistance.
|
/ai-review |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The symbol layer is in good shape now — decorated, class-qualified names, one 1. if (cornerRadius > 0.0f && !g_settings.roundMaximized &&
CoversWorkArea(borderRect)) {
cornerRadius = 0.0f;
}There is no Enabling the mod makes those windows less rounded than the Windows default, which is the opposite of what the mod advertises. It also silently zeroes Custom Window Corner Radius' output for those windows if both mods are enabled. 2.
Per the call tree documented in custom-corner-radius.wh.cpp#L373-L391, the border is only one of two consumers of the radius. The other — On top of that, The fix for both items is the same: make the maximized test once, on the bool ShouldRound(void* pThis) {
if (!IsMaximizedOrSnapped(pThis)) {
return false;
}
if (g_settings.roundMaximized) {
return true;
}
HWND hwnd = HwndFromTopLevelWindow(pThis);
return hwnd && !IsZoomed(hwnd);
}used by all four 3. UINT dpi = GetDpiForSystem();
float value = g_settings.radius * (dpi ? dpi / 96.0f : 1.0f);The pull request description says this was changed to DWM's own per-window scaling via 4. The pull request template is still pasted into the mod README. README lines 68–85 are the "## Changelog" and "## Mod authorship" blocks from 5. Still no screenshot in the README. The description says one was added, but the README has no image. This is a purely visual mod, so a before/after shot (or a short GIF of snapping a window) matters a lot for users browsing the catalog — see custom-corner-radius.wh.cpp#L35. Only 6. "disabling the mod restores the default look immediately" is still inaccurate. Nothing forces a repaint on enable, disable or settings change, so already-composed windows keep their old corners until DWM refreshes their visuals. Please reword to say the change takes effect the next time a window's visuals are refreshed, and that no system files or settings are modified — otherwise a stale window reads as a bug. 7. Consolidation into Custom Window Corner Radius — the recommendation stands. You asked for a call on this, and it's ultimately the maintainer's, so this isn't something you need to act on unilaterally. For the record, the technical case: the two mods run in the same process, hook the same Items 1–6 are worth fixing either way, since they carry over. Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ready-for-reviewer |
|
Thank you for the submission. I agree with point 7 of the last review. I added it the custom-corner-radius mod here and credited you and your PR: What do you think? You can try the mod here: |
|
its good idea |
Updated GitHub link and compiler options in the mod file. Removed unnecessary changelog and authorship sections.
|
/ready-for-reviewer |
|
@leshaalexey |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. Nice cleanup since the last revision — the symbol hooks are now proper decorated names against 1. The maximized/snapped split is made at the wrong layer, and
The clean fix is to recover the 2. if (!IsRoundableMaximizedWindow(hwnd)) {
ForgetWindow(hwnd);
return;
}This drops the window from tracking without calling The same leak happens when the table is full — Fix: clear the region for tracked windows that stop qualifying, and use an if (!IsRoundableMaximizedWindow(hwnd)) {
if (ForgetWindow(hwnd)) { // returns true if it was tracked
SetWindowRgn(hwnd, nullptr, TRUE);
}
return;
}Worth noting in the README too — with 3. Unload can leave the region thread running inside a freed module — dwm.exe crash. PostThreadMessage(g_regionThreadId, WM_QUIT, 0, 0);
WaitForSingleObject(g_regionThread, 2000);Two independent ways this leaves a live thread behind:
In either case WaitForSingleObject(g_regionThread, INFINITE);and the queue must be guaranteed to exist before 4. The system-wide WinEvent hooks are installed even when the feature they serve is disabled, and they cover far more than needed. The region thread and both Start the thread and install the hooks only when 5. The region is rebuilt and re-applied on every event, even when nothing changed.
6. UINT dpi = GetDpiForSystem();On a mixed-DPI multi-monitor setup this is wrong for every window that isn't on the primary-DPI monitor: a snapped window on a 150% display gets an 8px radius where the shell uses 12px, and vice versa. Once the HWND is available from item 1, use 7. The README has no screenshot. The mod's entire effect is visual and there's no image in the readme block (the PR description says one was added, but the file at 8. Overlap with Custom Window Corner Radius. To put a number on the overlap you raised in the PR description: this mod targets the same process, hooks the same four Your argument that the two answer different questions is reasonable, but the maintainer's standing preference is to extend an existing mod over merging a near-neighbour, and here the shared surface is unusually large. My recommendation is to fold it into Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ai-review |
Submission reviewNote: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding. Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it. Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them. The mod is well documented and the hook set is sound. The main concerns are the window-tracking mechanism, which is fragile in ways that produce exactly the flicker the README warns about, and the relationship to Custom Window Corner Radius. 1. This is very likely better as an option in Custom Window Corner Radius. You raised this yourself, so here's the reasoning as input to that decision. The two mods hook the same four Worth noting too that the derivation is broader than the credit comment suggests — the symbol-hook list, the Ultimately a catalog call for the maintainer, not a code defect. 2. The Three separate failure modes, all ending in the same visible symptom (a maximized window gets rounded and flickers):
The fix that removes all three at once: recover the bool ShouldRound(void* pThis) {
if (!IsMaximizedOrSnapped(pThis)) {
return false;
}
HWND hwnd = HwndFromTopLevelWindow(pThis);
return hwnd && !IsZoomed(hwnd);
}and 3. UINT dpi = GetDpiForSystem();
float value = g_settings.radius * (dpi ? dpi / 96.0f : 1.0f);On a mixed-DPI multi-monitor setup this is the wrong scale factor for any window not on the primary-DPI monitor — an 8px fallback renders as 8px on a 150% monitor when the surrounding geometry expects 12px. Use the window's own DPI: Related: the PR description says the forced radius is scaled through 4. The README has no screenshot. The mod's entire purpose is a visible change, and the PR description says a screenshot was added, but the current README has no image. Please add a before/after (or a short GIF of snapping a window). Both Optional improvements
Minor polish — none of this affects users, so it's your call. Items 1-3 are moot if you take the
Functionality notes
Non-critical observations about the feature behavior itself.
Next steps:
See the review process for details. |
Keeps window corners rounded when a window is maximized or snapped, without touching the window state itself.
Addressed everything from the AI review, except where noted below.
1.
SystemParametersInfo(SPI_SETDRAGFULLWINDOWS)— removed. Corners now update the next time each window's visuals are refreshed, as suggested.2. Overlap with
custom-corner-radius— see the note at the bottom.3. Symbol resolution — the
explorer.exewarmer, the hand-rolled RVA cache (ResolvedRvas,GetModuleIdentity,ResolveBySymbols,StoreRvas,LoadRvas,MatchesFilterList,RunningInDwm, thekKey*constants) and the@include explorer.exeline are all gone. The mod now usesWindhawkUtils::HookSymbolsindwm.exeonly, with full decorated signatures. Symbol resolution insidedwm.exeworks fine — the earlier failure was on my side.Two of the radius getters are listed with several signature variants (const/non-const, private/public), because they differ between builds. Only
CTopLevelWindowoverloads are listed on purpose: those hooks passthistoIsMaximizedOrSnapped, so a same-named method on another class must not match. That was a real bug in the previous revision —wcsstrmatching on undecorated names bound to same-named methods of a different class, and the filter was then reading a foreignthis.4.
LoadLibraryW— removed,GetModuleHandleonly, bail out ifudwm.dllisn't loaded.5. System DPI — no longer derived by hand. A forced radius is scaled through
CTopLevelWindow::GetWindowData+CWindowData::ScaleForDpi, i.e. DWM's own per-window scaling. Both symbols are optional and the system DPI remains a fallback.Regarding "verify that hooking
GetDpiAdjustedFloatCornerRadiusis needed at all": on 10.0.26100.8972 it is. Tracing showsGetEffectiveCornerStylereturningDWMWCP_ROUNDandGetRadiusFromCornerStylereturning8.0for a maximized window — the squaring happens further down, and it's the...ForCurrentStyle/...DpiAdjusted...getters that return zero there. Only hooking the first two produced no rounding at all on this build.6. Developer-only settings —
dumpSymbols,noFilter,debugLogandforceRadiusremoved, along withLogThrottled. Forcing a radius is now automatic: it only happens when DWM returned zero for a surfaceIsMaximizedOrSnappedvouches for, so on builds where promoting the style is enough the hooks are no-ops. Two settings remain: corner style and fallback radius.7. Screenshot — added to the README.
Also applied from the optional list:
HasMultipleDwminitWarningsInLastMinuteguard (taken fromcustom-corner-radius, which is why this mod is GPL-3.0 — credited in a comment at the top),DWMWCP_*constants from<dwmapi.h>,WindhawkUtils::StringSetting,<windhawk_utils.h>, radius clamped instead of silently substituted,Wh_Logused directly.On the overlap with
custom-corner-radiusHappy to move this there as an option if that's preferred — say the word and I'll close this PR and open one against that mod instead.
The reason I think it may work better standalone: the two mods answer different questions.
custom-corner-radiuschanges the radius DWM already uses; this one restores rounding where Windows deliberately removes it, and every decision is gated onIsMaximizedOrSnappedrather than on a radius value. Folded in, it would mean a second, differently-scoped filter running through the same radius pipeline. But that's a judgement call about the catalog, not about the code, so I'll follow whatever you prefer.Changelog
This pull request introduces a new mod.
Mod authorship
If this pull request introduces a new mod, please complete the section below.
This mod was created by:
Please select the options that best apply. Your selection does not affect the acceptance criteria, but it helps reviewers understand the context of the code and provide relevant feedback.