Menu

#2171 [PATCH] Per-Monitor DPI Awareness on Windows

Bug
closed-fixed
5
2020-06-02
2020-04-26
Piotr Fusik
No

This patch adds support for switching between monitors of different pixel density (DPI).
This prevents blurred text when switching between laptop's display and an external monitor.
See:
https://docs.microsoft.com/pl-pl/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests
https://docs.microsoft.com/pl-pl/windows/win32/hidpi/wm-dpichanged

1 Attachments

Discussion

1 2 > >> (Page 1 of 2)
  • Neil Hodgson

    Neil Hodgson - 2020-04-27

    SciTE and Scintilla aren't per-monitor DPI aware. They aren't even really DPI aware. This used to behave better on earlier versions of Windows when (in my recollection, which might be wrong) GetDeviceCaps(CreateCompatibleDC(NULL), LOGPIXELSY) could be used to track the system DPI. However, on current Windows 10, this appears to stick to the value at application startup. Thus starting a new instance of SciTE will be adapted to the current DPI but changing DPI will not change the size of text inside Scintilla.

    Scintilla could be made to better track current DPI (calling GetDpiForSystem) but per-monitor DPI (GetDpiForWindow) doesn't fit in very nicely. Much of the drawing code in the platform layer has no access to the window it is drawing on or measuring for. Fixing this may require a change to the interface between the platform layer and platform-independent core so may have to wait until the next major version.

    Pretending to be per-monitor DPI aware will stop the blurred text but won't scale it correctly.

     
  • Piotr Fusik

    Piotr Fusik - 2020-04-28

    I see. Waiting for SciTE 5 then!

    I hate restarting SciTE and reopening a dozen files every time I dock or undock my laptop. Sharp text of wrong size (that I can zoom in or out) works better for me than blurred text.

     
  • Neil Hodgson

    Neil Hodgson - 2020-05-19
    • labels: scite --> scite, scintilla, win32, scaling
    • status: open --> open-fixed
    • assigned_to: Neil Hodgson
     
  • Neil Hodgson

    Neil Hodgson - 2020-05-19

    Committed for SciTE with [4430c5]. SciTE forwards WM_DPICHANGED to Scintilla.

    Implemented support for Scintilla with [134a38]. Scintilla checks if DPI has changed inside painting and invalidates cached data if it has. Responds to WM_DPICHANGED by invalidation and redraw.

     

    Related

    Commit: [4430c5]
    Commit: [134a38]

  • Zufu Liu

    Zufu Liu - 2020-05-19

    I think these change need test.
    Before these changes, Notepad2 computes font size zoom factor like following:
    1. get LOGPIXELSY of editor window on startup as defaultDPI
    2. get current DPI of editor window on startup (with GetDpiForWindow, GetDpiForMonitor, and LOGPIXELSY) and in WM_DPICHANGED (use HIWORD(wParam)) as currentDPI
    3. the zoom factor is currentDPI/defaultDPI.

    so for user with only one monitor at 200% scale, on application startup, the zoom factor is 1, no need to zoom. Font zoom is only needed when user dynamic change scale settings or move to monitor with different DPI, it's not needed after login out and re-login in.

    after these changes, the relations become complicated.

     
    • Neil Hodgson

      Neil Hodgson - 2020-05-19

      After this change, Scintilla becomes responsible for scaling text size. Applications should not scale text size due to monitor scaling but should forward WM_DPICHANGED to Scintilla.

      Having applications multiply all font sizes by the scaling factor when calling SCI_STYLEGETSIZE[FRACTIONAL] makes applications more complex.

       
  • Zufu Liu

    Zufu Liu - 2020-05-19

    However there are other styles that need scaled by application (with scale factor currentDPI/96): whitespace dot size, extra ascent/descent, line frame size etc.
    Application also need to scale the margin width.

    There is a remaining bug [bugs:#2063]
    Another is stroke width, I don't remember where is was previously discussed.

     

    Related

    Bugs: #2063

    • Neil Hodgson

      Neil Hodgson - 2020-05-19

      Some features are currently single-pixel, like stroke width, or a small number of pixels so the appropriate time to scale is a trade-off. Making 1.5-pixel wide lines which are then blurry is unpopular with many users wanting pixel-grid-aligned lines. If stroke width is to scale, then the application should be able to control when and how far scaling occurs.

      As text is multiple pixels in size and already non-grid-aligned it does not suffer the same issue.

       
  • Zufu Liu

    Zufu Liu - 2020-05-19

    There are still some difference.
    Notepad2 current:

    // scale in application
    points = MulDiv(points, current window DPI, LOGPIXELSY on default monitor)
    // Scintilla old
    DeviceHeightFont = MulDiv(points, LOGPIXELSY on current monitor, 72)
    

    Scintilla current:

    DeviceHeightFont = MulDiv(points, current window DPI, 72)
    

    I actually simulate GetSystemMetricsForDpi() with:

    MulDiv(GetSystemMetrics(nIndex), current window DPI, LOGPIXELSY on default monitor)
    

    The default monitor is where application first displayed in on startup.
    I will need to check again whether LOGPIXELSY is same on all monitor.

     
  • Zufu Liu

    Zufu Liu - 2020-05-19

    OK the font scaling is fixed, checked the document at:

    https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-getdevicecaps

    LOGPIXELSY
    Number of pixels per logical inch along the screen height. In a system with multiple display monitors, this value is the same for all monitors.

     
  • Zufu Liu

    Zufu Liu - 2020-05-20

    SciTE seems still has something wrong (the candidate window is short than others, font is also smaller than others), see the screenshot.

    add sizeZoomed = MulDiv(sizeZoomed, 175, 100); in ScintillaWin::ImeStartComposition() has no help.

    Run Notepad2, Notepad, SciTE with font Microsoft YaHei UI, size 11 on 1920x1080, manually change scale from 100% to 175%.

     
    • Neil Hodgson

      Neil Hodgson - 2020-05-20

      That code is taking the effect of the DPI into account. The lf.lfHeight is -14 for 100%, -25 for 175%, and -29 for 200%.

       
      • Zufu Liu

        Zufu Liu - 2020-05-21

        They indeed computed to the same value (deviceHeight=2566), and it only affect the composition window, not the candidate window. I still not figured out what caused the shorter candidate window in SciTE (though text in editor window rendered correctly).

        A potential fix lf.lfHeight:

        #ifndef USER_DEFAULT_SCREEN_DPI
        #define USER_DEFAULT_SCREEN_DPI     96
        #endif
        
        // void ScintillaWin::ImeStartComposition()
        AutoSurface surface(this);
        const int logPixelsY = surface ? surface->LogPixelsY() : USER_DEFAULT_SCREEN_DPI;
        // The negative is to allow for leading
        lf.lfHeight = -MulDiv(sizeZoomed, logPixelsY, 72*SC_FONT_SIZE_MULTIPLIER);
        

        USER_DEFAULT_SCREEN_DPI can be defined and used to replace other 96.0 or 96.0f.

         
  • Piotr Fusik

    Piotr Fusik - 2020-05-20

    Many thanks for this change! I have built SciTE myself to try it. The text remains sharp when switching the monitors, that's fantastic. The tabs in SciTE don't scale well though - they become noticeably larger when switching from a laptop display to a monitor.

     
    • Neil Hodgson

      Neil Hodgson - 2020-05-22

      The tab bar should be fixed with [979191].

      The worst remaining issue is with strips but they may require deleting then recreating each control. I'll leave strips alone for now.

       

      Related

      Commit: [979191]

      • Piotr Fusik

        Piotr Fusik - 2020-05-22

        I confirm the tabs are fine now. The size of the Ctrl+F strip is a minor issue for me.

         
  • Zufu Liu

    Zufu Liu - 2020-05-21

    The bug for SciTE seems because it's not manifested to support Windows 10, update SciTE.exe.manifest to list Win 10 as a supported OS fixed the IME candidate window bug.
    Here is the updated manifest, changed dpiAwareness (based on Notepad2's), https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests

    There is a remaining bug: the reverseArrowCursor need recreate/resize after DPI changed.

    Edit: Need to create a new reverse arrow cursor for new DPI, without destroying old one.

    A possible doc update:

    Applications should forward WM_DPICHANGED to Scintilla in addition to manifest them support per-monitor DPI Awareness and corresponding Windows systems.
    
     

    Last edit: Zufu Liu 2020-05-21
    • Neil Hodgson

      Neil Hodgson - 2020-05-22

      Added compatibility section with [48fb41].

      At 200%, the IME seems excessively large to me. I'd expect the aim would be to match the size the text will be in the document but if this is what is wanted for Chinese then so be it.

       

      Related

      Commit: [48fb41]

    • Neil Hodgson

      Neil Hodgson - 2020-05-22

      Mentioned WM_DPICHANGED in documentation. Whether the application is DPI aware is really an application decision.

       
  • Zufu Liu

    Zufu Liu - 2020-05-22

    GetDpiForMonitor is missing from DpiForWindow.

    HMONITOR hMonitor = MonitorFromWindow(HwndFromWindowID(wid), MONITOR_DEFAULTTONEAREST);
    UINT dpiX = 0;
    UINT dpiY = 0;
    if (fnGetDpiForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) {
        dpi = dpiY;
    }
    

    https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getdpiformonitor

     
    • Neil Hodgson

      Neil Hodgson - 2020-05-22

      Why do you want this? If its just to support old versions of Windows then its not really worth the effort.

       
  • Zufu Liu

    Zufu Liu - 2020-05-24

    I find dpi in ScintillaWin is not initialized and updated in WM_DPICHANGED.
    it's checked every time in WndPaint(), will this reduce performance?

    ScintillaWin::ScintillaWin(HWND hwnd)
    dpi = DpiForWindow(hwnd);
    
    case WM_DPICHANGED:
    dpi = HIWORD(wParam);
    InvalidateStyleRedraw();
    
     
    • Zufu Liu

      Zufu Liu - 2020-05-24

      ct.CallTipCancel();
      ac.Cancel();

      are missing from WM_DPICHANGED.

       
      • Neil Hodgson

        Neil Hodgson - 2020-05-25

        How do you provoke WM_DPICHANGED while either of these are active? Selecting the Settings app or moving the window closes them.

         
        • Zufu Liu

          Zufu Liu - 2020-05-25

          Drag application window, move to anothor monitor.
          for SciTE, press Ctrl+Enter, ac shows, then click title bar, move SciTE, ac is at old position.

           
1 2 > >> (Page 1 of 2)

Log in to post a comment.