Skip to content

Fix: Per-Monitor V2 DPI awareness silently not taking effect - #65

Closed
mojingjing-git wants to merge 2 commits into
lukaslangrock:masterfrom
mojingjing-git:fix/per-monitor-v2-dpi-awareness
Closed

mojingjing-git wants to merge 2 commits into
lukaslangrock:masterfrom
mojingjing-git:fix/per-monitor-v2-dpi-awareness

Conversation

@mojingjing-git

Copy link
Copy Markdown

AI disclosure — please read first

This pull request was written by an AI coding agent (MiniMax M3.1) on behalf of a human contributor.

The investigation of the DPI configuration, the proposed fix, the commit message and this description were all generated by the model. A human reviewed the resulting diff before submitting and takes full responsibility for it.

If you'd prefer not to accept AI-generated contributions, feel free to close this without any explanation — that is a completely reasonable position and no offense is intended. What matters is that you know where it came from.


The problem

App.config already asks for PerMonitorV2:

<add key="DpiAwareness" value="PerMonitorV2"/>

…but app.manifest only declares the legacy:

<dpiAware xmlns="...">true</dpiAware>

which means System DPI aware. Because the manifest marks the process as System DPI aware at process startup, the subsequent PerMonitorV2 request can no longer upgrade the process — the two declarations contradict each other, and the manifest wins. On top of that, EnableWindowsFormsHighDpiAutoResizing was never set.

The practical result: Windows bitmap-stretches the whole application instead of letting it render at true pixel size. On a 175%-scaled display this shows up as blurry text and controls that no longer line up.

The fix

Two config files, 3 lines added, 1 line changed:

  • app.manifest — true/pm plus the Windows 10 1703+ <dpiAwareness> element requesting PerMonitorV2, PerMonitor (the second value is the documented fallback).
  • App.config — add EnableWindowsFormsHighDpiAutoResizing so WinForms actually scales forms and controls to the correct factor.

Why this is worth fixing regardless of language

This is not locale-specific and not cosmetic-only. It affects every user on a scaled display (125% / 150% / 175% / 200%).

There is also a direct interaction with existing code. Menu.cs, Settings.cs and InputBox.cs each contain:

// Prevent font-fallback and subsequent layout issues...
this.Font = new Font("Microsoft Sans Serif", 8.25f, FontStyle.Regular, GraphicsUnit.Point, 0);

Forcing a single non-fallback font is exactly the kind of mitigation you add when the app is bitmap-stretched — under proper Per-Monitor V2 rendering that concern largely goes away, and the hard-coded font can be relaxed later if you want.

Verification

I built the project from a clean checkout of master with only this patch applied, and launched the resulting binary on Windows 11 at 175% scaling (DPI 168) — it starts and renders correctly.

I should be upfront about a limitation: I was not able to capture before/after screenshots from my environment, so there is no visual evidence attached here. If you'd like screenshots (or a reproducible before/after build) to decide on this, just ask and I can provide them.

Notes

  • No source code, control names, event bindings or string literals were touched — this only changes DPI declarations.
  • License and attribution are untouched.
  • Happy to split this into two separate PRs (manifest / App.config) if you prefer reviewing them independently.

App.config already requests PerMonitorV2, but app.manifest declares only the legacy <dpiAware>true</dpiAware>, i.e. System DPI aware. Because the manifest makes the process System DPI aware at startup, the later PerMonitorV2 request can no longer upgrade the process. On top of that, EnableWindowsFormsHighDpiAutoResizing was never set.

The net effect is that Windows bitmap-stretches the app instead of letting it render at the real pixel size, so text looks blurry and controls end up misaligned on displays scaled to 125%/150%/175%.

- app.manifest: declare true/pm plus the Windows 10 1703+ <dpiAwareness> element requesting PerMonitorV2, falling back to PerMonitor
- App.config: enable WinForms high-DPI auto resizing so forms and controls are scaled to the correct factor

Reported from a machine running Windows 11 at 175% scaling.
mojingjing-git added a commit to mojingjing-git/ShutdownTimerClassic that referenced this pull request Oct 14, 2026
Localization

Full zh-CN translation covering the main menu, countdown window, tray context menu, all three settings tabs, the input dialog, every message box and balloon notification, and the About rich-text block. Fonts switched from Microsoft Sans Serif to Microsoft YaHei UI to avoid CJK glyph fallback.

The upstream code uses the combo box Text as its internal action identifier: Timer.ExecutePowerAction switch-cases on the literal English strings, the tray theme is compared against "Light"/"Dark", and the settings JSON persists those same English values. Translating the drop-down entries directly would break action dispatch and invalidate existing settings files.

LocalizedCatalog.cs introduces a display/value split. The drop-down renders Chinese through an overridden ToString() while Value keeps the original English identifier, and call sites use GetValue()/SelectValue() to read and write the English value. Result: the UI is Chinese while settings files and CLI behaviour remain compatible with the English build.

DPI fix

app.manifest declared the legacy <dpiAware>true</dpiAware> (System DPI aware) while App.config requested PerMonitorV2. The manifest wins at process startup, so the PerMonitorV2 request could never take effect, and EnableWindowsFormsHighDpiAutoResizing was never set, leaving Windows to bitmap-stretch the app on scaled displays. Now declared via true/pm plus the Windows 10 1703+ <dpiAwareness> element, with WinForms auto resizing enabled. Submitted upstream as lukaslangrock#65.

Also moves the target framework from v4.8 to v4.8.1 and keeps the upstream English README as README.upstream.md.

Localization and fixes were produced by an AI coding agent (MiniMax M3.1) and reviewed by a human maintainer. The original MIT licence and the copyright notice of Lukas Langrock are preserved.
@lukaslangrock

Copy link
Copy Markdown
Owner

This seems like a valid issue at first glance, I'll check out the change soon, but in the future, please write the PR description yourself. It's fine to use AI for debugging and even some code generation, and in this case the actual code change is really small, so that's also fine, but I am really not looking forward to reading AI generated reports :)

@lukaslangrock lukaslangrock self-assigned this Oct 14, 2026
@mojingjing-git

Copy link
Copy Markdown
Author

Thanks, I'll write my own descriptions next time. My
English just isn't good enough. :(

@lukaslangrock

Copy link
Copy Markdown
Owner

That's absolutely fine! AI translations are also totally fine if you are more comfortable in your native language :)

I just took your PR for a spin, and this really fixes a common issue with the DPI-aware scaling, but the analysis of the LLM is a bit flawed:

Windows bitmap-stretches the whole application instead of letting it render at true pixel size. On a 175%-scaled display this shows up as blurry text and controls that no longer line up.

The app does adapt to a (fractionally) scaled UI, however it only support exactly one scaling, which should be the one of your primary monitor. Roaming between displays of a different scaling does cause the blurry behaviour. Also, I never noted controls not lining up (before). Can you share some more details on that issue?

But the good part: this PR indeed fixes roaming between displays. However, I found that in that exact scenario, the layout breaks as well, which would match the issue the LLM described as the before-behaviour:

grafik

I still need to figure out why and how I could fix that, if the approach of solely defining PerMonitorV2 in App.config is indeed the idiomatic fix. So the concern about layout issues doesn't match in the current state, instead it occurs only when we apply the changes of this PR (atleast from my testing, feel free to chime in as well if you notice a different behaviour).

So, tldr: The concern of the LLM that PerMonitorV2 is silently forfeited to PerMonitor (V1) seems valid, but the scaling is definetly at least somewhat bugged.

Some questions about the exact changes

Upon looking into the topic and trying some things for myself, I noticed that just removing the line <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware> leads to - as it seems to be - identical behaviour to your PR. This would match the stated reason, that that line in app.manifest really does overwrite the asked-for PerMonitorV2 scaling from App.config.

Can you elaborate on why you added the other options (as listed below) to app.manifest and App.config?

      <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
    <add key="EnableWindowsFormsHighDpiAutoResizing" value="true"/>

…ling that goes with it

Two separate things were going on, so this PR now does both.

1. The DPI awareness declaration

The manifest declared <dpiAware>true</dpiAware>, which makes the process
system-DPI-aware. The App.config already asked for DpiAwareness=PerMonitorV2,
but a manifest declaration overrides the app.config setting, so PerMonitorV2
never took effect. The app was bitmap-stretched on a scaled display and did
not re-layout when moved to a display with a different scale.

Per the WinForms high-DPI documentation, the manifest is the wrong place for
this on .NET Framework 4.7+ ("This approach is no longer recommended, since it
overrides settings defined on the app.config file"). Removing the dpiAware
element from the manifest makes the app.config the single source of truth,
which is enough on its own. So the manifest no longer declares DPI awareness
at all.

The EnableWindowsFormsHighDpiAutoResizing key I had added is dropped again:
with DpiAwareness set to PerMonitorV2, dynamic DPI support is already on by
default. The key is only there to opt out. App.config is now unchanged from
master.

2. The layout breaking on a DPI change

This part is independent of the manifest - it happens with the manifest
removed just the same.

All four forms use AutoScaleMode.Font with AutoScaleDimensions (6, 13). The
documentation notes that font based scaling "might not be linear when the app
runs as system DPI-aware or per-monitor v2 DPI-aware" and can "cause
discrepancies in height-to-width ratios", and that Label and TextBox always
scale by font regardless of AutoScaleMode.

So as soon as dynamic DPI support kicks in, the form is scaled by the DPI
ratio while its labels are scaled by the (non-linear) font ratio. The two
disagree and the text gets clipped.

Switching the forms to AutoScaleMode.Dpi with AutoScaleDimensions (96, 96)
gives linear, DPI-native scaling.

Tested on a 175% primary display with a 125% secondary display, moving the
window back and forth across the two:

- before: version string clipped to "v1.3", the colon after "Select an
  action:" gone, "Hours"/"Minutes" cut off
- after: Menu, Settings and Countdown all keep their layout and text intact
- control run with only the manifest change and AutoScaleMode.Font kept: the
  clipping is still there, which is what points at the scaling mode rather
  than at the manifest
@mojingjing-git

Copy link
Copy Markdown
Author

I think I may have found the real issue—you can take a look at the latest PR

@lukaslangrock

Copy link
Copy Markdown
Owner

Which should I look at, the new PR or this one? Does the new PR supersede this one?

@mojingjing-git

Copy link
Copy Markdown
Author

Which should I look at, the new PR or this one? Does the new PR supersede this one?

New one(#66),I will close this PR:)

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.

3 participants