Skip to content

Fix: Improve Per-Monitor DPI Scaling on Windows to Address Blurriness#47322

Closed
utahisnotastate wants to merge 2 commits intoelectron:mainfrom
utahisnotastate:fix-win11-dpi-tests
Closed

Fix: Improve Per-Monitor DPI Scaling on Windows to Address Blurriness#47322
utahisnotastate wants to merge 2 commits intoelectron:mainfrom
utahisnotastate:fix-win11-dpi-tests

Conversation

@utahisnotastate
Copy link
Copy Markdown

@utahisnotastate utahisnotastate commented May 31, 2025

Problem:
Electron applications on Windows 11 can exhibit blurriness when moved between monitors with different DPI scaling factors or when window focus changes. This is attributed to Windows 11's aggressive DPI management for unfocused windows and Electron not fully implementing Per-Monitor DPI Awareness version 2 (PMv2), leading to improper bitmap scaling.

Solution:
This Pull Request implements robust Per-Monitor DPI V2 awareness and handling for Electron on Windows by:

  1. Updating Application Manifest:

    • The shell/browser/resources/win/dpi_aware.manifest has been updated to declare <dpiAwareness>PerMonitorV2, System</dpiAwareness>, with a fallback to <dpiAware>true/pm</dpiAware>. This explicitly enables Per-Monitor DPI Awareness v2, which is the recommended setting for modern Windows applications to correctly handle dynamic DPI changes.
  2. Implementing WM_DPICHANGED Handler:

    • A handler for the WM_DPICHANGED Windows message has been added to NativeWindowViews::PreHandleMSG.
    • When this message is received (indicating a DPI change for the window):
      • The window is immediately resized and repositioned using SetWindowPos based on the pixel rectangle suggested by Windows in the message's lParam. This ensures the window's physical size correctly adapts to the new DPI.
      • A new method, NativeWindowViews::OnHostDpiChanged(int new_dpi), is invoked to propagate the DPI change within Electron's view system.
    • The OnHostDpiChanged method updates internal DPI tracking and forces a re-layout and repaint of the window's non-client frame view and root view, ensuring custom UI elements refresh correctly at the new DPI.
  3. Leveraging Existing DPI-Aware Views Logic:

    • Electron's existing WinFrameView (for custom window frames) and other view components already utilize DPI-aware system calls (e.g., GetSystemMetricsInDIP, GetDPIScale) for their layout and painting. The new WM_DPICHANGED handling and subsequent layout invalidations are designed to trigger these existing mechanisms, allowing them to use the updated DPI information from the OS.

Testing:

  • Manual Testing (Recommended):
    • Verify on Windows 11 with multiple monitors set to different DPI scaling factors (e.g., 100% and 150%/200%).
    • Test by moving windows between monitors.
    • Test by changing window focus on high-DPI monitors.
    • Test with maximized, restored, and frameless windows.
    • Confirm that window content (web views, custom frames, title bars, icons) remains crisp and correctly scaled.
  • Automated Tests:
    • Added C++ unit tests in shell/browser/native_window_views_win_unittest.cc.
    • These tests verify:
      • Correct dispatch of WM_DPICHANGED within NativeWindowViews::PreHandleMSG to the new OnHostDpiChanged method.
      • Proper updating of internal DPI state in NativeWindowViews.
      • That relevant views (root view, non-client frame view) are marked for re-layout after a DPI change notification.
    • A new GTest target, electron_browser_unittests_win, has been added to the root BUILD.gn to compile and run these tests on Windows.

Impact:
These changes should significantly improve the visual experience for Electron app users on Windows 11 multi-monitor setups with varying DPIs, reducing or eliminating the reported blurriness.

…iews.

This commit introduces C++ unit tests for the recently added Windows-specific
DPI change handling logic in `NativeWindowViews`. These tests aim to ensure
the correct processing of `WM_DPICHANGED` messages and the subsequent
internal state updates and view refreshes.

Changes include:

1.  **New Test File:**
    - Created `shell/browser/native_window_views_win_unittest.cc` to house
      the new unit tests.

2.  **New Test Target:**
    - Added a new test target `electron_browser_unittests_win` to the root
      `BUILD.gn` file. This target compiles the new unit tests for Windows builds.

3.  **Tests for `NativeWindowViews::PreHandleMSG`:**
    - Verified that when `WM_DPICHANGED` is processed:
        - `NativeWindowViews::OnHostDpiChanged` is invoked with the correct new DPI.
        - The message is correctly marked as handled (return value and result code).
        - Other messages are ignored by this specific handler.

4.  **Tests for `NativeWindowViews::OnHostDpiChanged`:**
    - Verified that:
        - The internal `current_dpi_` member of `NativeWindowViews` is updated.
        - The `needs_layout` flag is set on the widget's root view and
          non-client frame view, indicating they are prepared for re-layout.

5.  **Code Modification for Testability:**
    - Changed the `current_dpi_` member in `NativeWindowViews` from `private`
      to `protected` to allow the derived test class to access it for
      verification.
    - Added a public getter `GetCurrentDpi()` to the `TestNativeWindowViews`
      mock/test class.

These tests provide coverage for the core logic responsible for making Electron
windows adapt to DPI changes triggered by `WM_DPICHANGED`, contributing to
the fix for blurriness issues on Windows 11.
@welcome
Copy link
Copy Markdown

welcome bot commented May 31, 2025

💖 Thanks for opening this pull request! 💖

Semantic PR titles

We use semantic commit messages to streamline the release process. Before your pull request can be merged, you should update your pull request title to start with a semantic prefix.

Examples of commit messages with semantic prefixes:

  • fix: don't overwrite prevent_default if default wasn't prevented
  • feat: add app.isPackaged() method
  • docs: app.isDefaultProtocolClient is now available on Linux

Commit signing

This repo enforces commit signatures for all incoming PRs.
To sign your commits, see GitHub's documentation on Telling Git about your signing key.

PR tips

Things that will help get your PR across the finish line:

  • Follow the JavaScript, C++, and Python coding style.
  • Run npm run lint locally to catch formatting errors earlier.
  • Document any user-facing changes you've made following the documentation styleguide.
  • Include tests when adding/changing behavior.
  • Include screenshots and animated GIFs whenever possible.

We get a lot of pull requests on this repo, so please be patient and we will get back to you as soon as we can.

@electron-cation electron-cation bot added the new-pr 🌱 PR opened recently label May 31, 2025
@utahisnotastate utahisnotastate changed the title Test: Add unit tests for Windows DPI change handling in NativeWindowV… Fix: Improve Per-Monitor DPI Scaling on Windows to Address Blurriness May 31, 2025
@TheKing-OfTime
Copy link
Copy Markdown

Will these changes also fix #10862 or/and #47322 ?
Workaround for these bugs in my app looks pretty sad. Besides, it does not affect all possible cases. So it would be great to see a proper fix on Electron's end.

Comment on lines +1944 to +1982
bool NativeWindowViews::PreHandleMSG(UINT message,
WPARAM w_param,
LPARAM l_param,
LRESULT* result) {
if (message == WM_DPICHANGED) {
WORD new_dpi_y = LOWORD(w_param);
RECT* const prcNewWindow = reinterpret_cast<RECT*>(l_param);

::SetWindowPos(GetAcceleratedWidget(),
nullptr,
prcNewWindow->left,
prcNewWindow->top,
prcNewWindow->right - prcNewWindow->left,
prcNewWindow->bottom - prcNewWindow->top,
SWP_NOZORDER | SWP_NOACTIVATE);

OnHostDpiChanged(new_dpi_y);
*result = 0;
return true;
}
// TODO(bridiver) handle other messages here
return false;
}

void NativeWindowViews::OnHostDpiChanged(int new_dpi) {
current_dpi_ = new_dpi;

if (widget()) {
widget()->UpdateWindowIcon();

if (widget()->non_client_view() && widget()->non_client_view()->frame_view()) {
widget()->non_client_view()->frame_view()->InvalidateLayout();
widget()->non_client_view()->frame_view()->SchedulePaint();
}

widget()->GetRootView()->Layout();
widget()->GetRootView()->SchedulePaint();
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems to duplicate what the default handing is performed in the runtime today via https://source.chromium.org/chromium/chromium/src/+/main:ui/views/win/hwnd_message_handler.cc;l=1876-1921 , can you clarify why this is needed ?

@codebytere
Copy link
Copy Markdown
Member

The quality of this PR doesn't meet our standards for a few reasons, including:

  • Adding unrelated test infrastructure.
  • Duplicated non-compiling code

Based on these oddities and Jules clearly identifiable in the commit, we suspect this PR of being entirely AI-generated. While Electron appreciates and encourages community contributions, its maintainers only have limited bandwidth. AI-generated PRs will not be reviewed and will be closed as invalid.

This PR will now be closed. In the future, raising more PRs with purely AI generated content will result in a organization level ban.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

invalid new-pr 🌱 PR opened recently

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants