Open dmex opened 1 year ago
Original PR reference #1347
@henrypp I've been making changes to the DPI implementation. Do you have any ideas for avoiding GetDpiForSystem and GetDpiForMonitor since they don't change at runtime (GetDpiForWindow doesn't have this issue)?
@dmex
I just checked GetDpiForMonitor
and it returns correct DPI for my monitors (same as GetDpiForWindow
).
To avoid using GetDpiForSystem
need passing WindowHandle
to functions where GetDpiForSystem
is used.
GetDpiForMonitor and it returns correct DPI for my monitors
I fixed this case by using GetShellWindow() with GetDpiForWindow().
To avoid using GetDpiForSystem
We can use GetShellWindow()+GetDpiForWindow or GetScaleFactorForMonitor (with or without GetShellWindow)
GetScaleFactorForMonitor returns the correct DPI for the monitor but not sure if it should use GetShellWindow or just NULL?
if (SUCCEEDED(GetScaleFactorForMonitor(MonitorFromWindow(NULL, MONITOR_DEFAULTTOPRIMARY), &value)))
{
UINT dpi = PhMultiplyDivideSigned(value, USER_DEFAULT_SCREEN_DPI, 100);
}
From here:
GetScaleFactorForMonitor() returns the correct result if the calling application has set its
dpi awareness to DPI_AWARENESS_UNAWARE.
If any of the others (DPI_AWARENESS_SYSTEM_AWARE, DPI_AWARENESS_PER_MONITOR_AWARE) is set, then
the results are incorrect.
System Informer now is under DPI_AWARENESS_PER_MONITOR_AWARE.
@henrypp
It works for me and GetScaleFactorForMonitor changes at runtime when changing the display properties.
He might be right about the UWP aspect.... The IDisplayPropertiesStatics_get_ResolutionScale method returns the correct scaling at 150% and so does the DPI. Can you compile this and compare the output?
#include <roapi.h>
#include <windows.graphics.display.h>
#pragma comment(lib, "runtimeobject.lib")
DEFINE_GUID(IID___x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformation, 0xBED112AE, 0xADC3, 0x4DC9, 0xAE, 0x65, 0x85, 0x1F, 0x4D, 0x7D, 0x47, 0x99);
DEFINE_GUID(IID___x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformationStatics, 0xc6a02a6c, 0xd452, 0x44dc, 0xba, 0x07, 0x96, 0xf3, 0xc6, 0xad, 0xf9, 0xd1);
DEFINE_GUID(IID___x_ABI_CWindows_CGraphics_CDisplay_CIDisplayPropertiesStatics, 0x6937ed8d, 0x30ea, 0x4ded, 0x82, 0x71, 0x45, 0x53, 0xff, 0x02, 0xf6, 0x8a);
VOID CheckUWP()
{
__x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformationStatics* displayInformation;
__x_ABI_CWindows_CGraphics_CDisplay_CIDisplayPropertiesStatics* displayProperties;
HSTRING stringHandle;
HRESULT hr;
WindowsCreateString(RuntimeClass_Windows_Graphics_Display_DisplayInformation, (UINT)wcslen(RuntimeClass_Windows_Graphics_Display_DisplayInformation), &stringHandle);
hr = RoGetActivationFactory(stringHandle, &IID___x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformationStatics, (IInspectable**)&displayInformation);
if (SUCCEEDED(hr))
{
__x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformation* display_information;
__x_ABI_CWindows_CGraphics_CDisplay_CResolutionScale resolution_scale;
hr = __x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformationStatics_GetForCurrentView(displayInformation, &display_information);
if (SUCCEEDED(hr))
{
hr = __x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformation_get_ResolutionScale(display_information, &resolution_scale);
if (SUCCEEDED(hr))
{
float value = (float)(resolution_scale / 100.0f);
dprintf("");
}
}
}
WindowsCreateString(RuntimeClass_Windows_Graphics_Display_DisplayProperties, (UINT)wcslen(RuntimeClass_Windows_Graphics_Display_DisplayProperties), &stringHandle);
hr = RoGetActivationFactory(stringHandle, &IID___x_ABI_CWindows_CGraphics_CDisplay_CIDisplayPropertiesStatics, (IInspectable**)&displayProperties);
if (SUCCEEDED(hr))
{
__x_ABI_CWindows_CGraphics_CDisplay_CResolutionScale scale;
FLOAT dpi;
hr = __x_ABI_CWindows_CGraphics_CDisplay_CIDisplayPropertiesStatics_get_ResolutionScale(displayProperties, &scale);
if (SUCCEEDED(hr))
{
dprintf("");
}
hr = __x_ABI_CWindows_CGraphics_CDisplay_CIDisplayPropertiesStatics_get_LogicalDpi(displayProperties, &dpi);
if (SUCCEEDED(hr))
{
FLOAT value = (FLOAT)dpi / 96.0f;
dprintf("");
}
}
}
GetDpiForMonitor: 96
GetDpiForWindow: 96
GetScaleFactorForMonitor: 100
GetDeviceCaps: 96
__x_ABI_CWindows_CGraphics_CDisplay_CIDisplayPropertiesStatics_get_ResolutionScale: 100
GetDpiForMonitor: 120
GetDpiForWindow: 120
GetScaleFactorForMonitor: 125
GetDeviceCaps: 120
__x_ABI_CWindows_CGraphics_CDisplay_CIDisplayPropertiesStatics_get_ResolutionScale: 125
Looks like it returns correct results, but GetScaleFactorForMonitor
and winrt
return values not for MulDiv
, and just says dpi value in human readable format. Thats why i did not understand why you are going to use GetScaleFactorForMonitor
.
Again, GetDpiForMonitor
is V2 aware and return correct result when dpi changed for monitor. Maybe your test builds not enabled PerMonitorv2 support?
Here is working binary with PerMonitorv2 enabled. On start waiting 2 seconds. Just move cursor to monitor to display it's DPI.
Maybe your test builds not enabled PerMonitorv2 support?
The documentation says it's not V2 aware? https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getdpiformonitor#remarks
should not be used if the calling thread is per-monitor DPI aware.
Interesting comment about this api from Microsoft ;)
As you see Microsoft itself using GetDpiForMonitor
although Terminal is PerMonitor aware.
When you call GetDpiForMonitor, you will receive different DPI values depending on the DPI awareness of the calling application.
It exactly says GetDpiForMonitor
is not dpi aware (ONLY!) when you are running not under PROCESS_PER_MONITOR_DPI_AWARE
.
And how should not be used if the calling thread is per-monitor DPI aware
appears in official documentation i did not understand.
Where is here "not PerMonitor aware":
PROCESS_PER_MONITOR_DPI_AWARE
returns The actual DPI value set by the user for that display
.
As you can see, official documentation is contradicts itself.
Per Monitor V1 is coming starts 8.1 when GetDpiForMonitor
is added, it looks like official documentation required changes to should not be used if the calling thread is *not* per-monitor DPI aware
.
@henrypp
Are we able to eliminate DPI from application settings? PhLoadWindowPlacementFromSetting/PhSaveWindowPlacementToSetting for example are both saving the DPI but this should be redundant. Application settings should be changed to logical units which don't require persisting DPI and then we convert to physical units at runtime so those settings are portable across all machines?
For example:
https://github.com/microsoft/Windows-task-snippets/blob/master/tasks/Convert-DPI-rectangles.md
https://bitbucket.org/chromiumembedded/cef/commits/a5a5e7f
https://github.com/raysan5/raylib/issues/1982#issuecomment-916075719
Are we able to eliminate DPI from application settings?
Already!
PhGetSizeDpiValue is used to avoid saving dpi value in configuration, how it works you can see in layout manager, saving the packed size value and on resizing unpack it's size to current dpi value and doing the job. It can be implemented in PhLoadWindowPlacementFromSetting
/PhSaveWindowPlacementToSetting
without saving actual dpi value.
But, i think some changes is required for PhGetSizeDpiValue
, like do not change left/right values, change only width and height, some maths is required to transit RECT
into PH_RECTANGLE
. My misstake ;)
@henrypp
We're still saving the DPI and scaling on startup? There's some weird issue where hidden treelist window columns are incorrectly scaled when their window is hidden and starting/closing/restarting the application with different DPI values:
Although it's probably caused by GetDpiForSystem returning the wrong DPI until the application is restarted?
GetScaleFactorForMonitor and winrt return values not for MulDiv, and just says dpi value in human readable format. Thats why i did not understand why you are going to use GetScaleFactorForMonitor.
The enums/values returned by winrt can be used with muldiv by casting those values to float. I'm looking for alternatives to GetDpiForSystem since it has a major problem when you change the scaling at runtime from the display properties.
If the current display properties are using 100% then GetDpiForSystem returns 96 but continues returning 96 even after changing the scaling and doesn't update unless you restart the application. GetDpiForWindow will instantly return the newer scaling but parts of the application (i.e settings) using GetDpiForSystem end up saving the 96 DPI but the application is using 120 DPI and with RECTs from 120 - When you restart the application it sees 96 != 120 and scales those values but they were already scaled for 120.
System: 96, Window: 96, Monitor: 120, Monitor2: 120, DeviceCaps: 96
<User changed display settings>
System: 96, Window: 120, Monitor: 120, Monitor2: 120, DeviceCaps: 96
You can call this in a loop and see GetDpiForSystem return the wrong values:
VOID PrintDPI(VOID)
{
static PH_INITONCE initOnce = PH_INITONCE_INIT;
static HRESULT (WINAPI *GetDpiForMonitor_I)(
_In_ HMONITOR hmonitor,
_In_ MONITOR_DPI_TYPE dpiType,
_Out_ PUINT dpiX,
_Out_ PUINT dpiY
) = NULL; // win81+
static UINT (WINAPI *GetDpiForWindow_I)(
_In_ HWND hwnd
) = NULL; // win10rs1+
static UINT (WINAPI *GetDpiForSystem_I)(
VOID
) = NULL; // win10rs1+
static UINT (WINAPI *GetDpiForSession_I)(
VOID
) = NULL; // ordinal 2713
if (PhBeginInitOnce(&initOnce))
{
PVOID baseAddress;
if (!(baseAddress = PhGetLoaderEntryDllBase(L"shcore.dll")))
baseAddress = PhLoadLibrary(L"shcore.dll");
if (baseAddress)
{
GetDpiForMonitor_I = PhGetProcedureAddress(baseAddress, "GetDpiForMonitor", 0);
}
if (!(baseAddress = PhGetLoaderEntryDllBase(L"user32.dll")))
baseAddress = PhLoadLibrary(L"user32.dll");
if (baseAddress)
{
GetDpiForWindow_I = PhGetProcedureAddress(baseAddress, "GetDpiForWindow", 0);
GetDpiForSystem_I = PhGetProcedureAddress(baseAddress, "GetDpiForSystem", 0);
}
PhEndInitOnce(&initOnce);
}
UINT systemDpi = 0;
UINT shellWindowDpi = 0;
UINT monitorWindowDpi = 0;
UINT monitorRectWindowDpi = 0;
UINT deviceCapsDpi = 0;
{
systemDpi = GetDpiForSystem_I();
shellWindowDpi = GetDpiForWindow_I(GetShellWindow());
}
{
UINT dpi_x;
UINT dpi_y;
GetDpiForMonitor_I(MonitorFromWindow(GetShellWindow(), MONITOR_DEFAULTTONEAREST), MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y);
monitorWindowDpi = dpi_x;
}
{
UINT dpi_x;
UINT dpi_y;
GetDpiForMonitor_I(MonitorFromRect(&(RECT){ 1, 1, 1, 1 }, MONITOR_DEFAULTTONEAREST), MDT_EFFECTIVE_DPI, &dpi_x, &dpi_y);
monitorRectWindowDpi = dpi_x;
}
{
HDC screenHdc;
if (screenHdc = GetDC(NULL))
{
deviceCapsDpi = GetDeviceCaps(screenHdc, LOGPIXELSX);
ReleaseDC(NULL, screenHdc);
}
}
dprintf("System: %lu, Window: %lu, Monitor: %lu, Monitor2: %lu, DeviceCaps: %lu\n", systemDpi, shellWindowDpi, monitorWindowDpi, monitorRectWindowDpi, deviceCapsDpi);
}
BTW the above code with IDisplayInformationStatics wasn't working because the runtime wasn't initialized. IDisplayProperties is deprecated and probably doesn't return the correct values. This code should work:
#include <roapi.h>
#include <windows.graphics.display.h>
#pragma comment(lib, "runtimeobject.lib")
DEFINE_GUID(IID___x_ABI_CWindows_CUI_CXaml_CHosting_CIWindowsXamlManagerStatics, 0x28258A12, 0x7D82, 0x505B, 0xB2, 0x10, 0x71, 0x2B, 0x04, 0xA5, 0x88, 0x82);
DEFINE_GUID(IID___x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformationStatics, 0xc6a02a6c, 0xd452, 0x44dc, 0xba, 0x07, 0x96, 0xf3, 0xc6, 0xad, 0xf9, 0xd1);
VOID CheckUWP()
{
__x_ABI_CWindows_CUI_CXaml_CHosting_CIWindowsXamlManagerStatics* manager;
__x_ABI_CWindows_CUI_CXaml_CHosting_CIWindowsXamlManager* result;
__x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformationStatics* displayInformation;
__x_ABI_CWindows_CGraphics_CDisplay_CIDisplayPropertiesStatics* displayProperties;
HSTRING stringHandle;
HRESULT hr;
WindowsCreateString(RuntimeClass_Windows_UI_Xaml_Hosting_WindowsXamlManager, (UINT)wcslen(RuntimeClass_Windows_UI_Xaml_Hosting_WindowsXamlManager), &stringHandle);
if (stringHandle)
{
hr = RoGetActivationFactory(stringHandle, &IID___x_ABI_CWindows_CUI_CXaml_CHosting_CIWindowsXamlManagerStatics, (IInspectable**)&manager);
if (SUCCEEDED(hr))
{
hr = __x_ABI_CWindows_CUI_CXaml_CHosting_CIWindowsXamlManagerStatics_InitializeForCurrentThread(manager, &result);
}
}
WindowsCreateString(RuntimeClass_Windows_Graphics_Display_DisplayInformation, (UINT)wcslen(RuntimeClass_Windows_Graphics_Display_DisplayInformation), &stringHandle);
hr = RoGetActivationFactory(stringHandle, &IID___x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformationStatics, (IInspectable**)&displayInformation);
if (SUCCEEDED(hr))
{
__x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformation* display_information;
hr = __x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformationStatics_GetForCurrentView(displayInformation, &display_information);
if (SUCCEEDED(hr))
{
__x_ABI_CWindows_CGraphics_CDisplay_CResolutionScale scale;
FLOAT dpi;
hr = __x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformation_get_ResolutionScale(display_information, &scale);
if (SUCCEEDED(hr))
{
dprintf("");
}
hr = __x_ABI_CWindows_CGraphics_CDisplay_CIDisplayInformation_get_LogicalDpi(display_information, &dpi);
if (SUCCEEDED(hr))
{
FLOAT value = (FLOAT)dpi / 96.0f;
dprintf("");
}
}
}
}
@henrypp
All the remaining DPI issues have been fixed and support now includes the application and plugins. Are you able to try out the latest nightly and let me know if there's any remaining DPI issues?
On a separate topic I've also found a major Windows 11 bug with the CreateDialog API and PerMonitorV2... If the user minimizes windows/dialogs that were created using the CreateDialog API and then changes the DPI settings for the display while the window is minimized the internal state of the window becomes deadlocked/corrupted. The message loop immediately stops processing messages, various window functions return incorrect data and if you attempt to active the window it'll trip the message loop then resize to 0,0 and become unusable.
I'm not sure if we can workaround the issue with CreateDialog by just changing some window styles?
All looks good on different monitors.
I'm not sure if we can workaround the issue with CreateDialog by just changing some window styles?
You can send bug to micro$oft, i think.
You can send bug to micro$oft, i think.
I have and they marked it "needs more details" 🤦
What about scaling columns sizes depending on DPI? On 100% display the sizes can be larger compared to displays with for example 125%. When moving the Main Window between such 2 displays I have to change the sizes each time.
@MagicAndre1981 in my project haved listview resize on dpi change, do not know how it realized here.
nice, maybe create a PR to add it here, too?
ok, there is no scaling on DPI change. When you start SI on 100% and move it to 125% display it still uses 100% scaling
The recent addition of V2 DPI awareness introduced a few issues: