Popular Post UEZ Posted August 12 Popular Post Posted August 12 (edited) I think the _WinAPI_DPI UDF is now complete enough to be released here. expandcollapse popup;Coded by UEZ build 2026-08-26 beta #include-once #include <GDIPlus.au3> #include <StructureConstants.au3> #include <WinAPIGdi.au3> #include <WinAPISysWin.au3> #include <WinAPIsysinfoConstants.au3> ;$SM_* constants for _WinAPI_GetSystemMetricsForDpi #Region DPI Constants ;https://learn.microsoft.com/en-us/windows/win32/api/windef/ne-windef-dpi_awareness Global Enum $DPI_AWARENESS_INVALID = -1, $DPI_AWARENESS_UNAWARE = 0, $DPI_AWARENESS_SYSTEM_AWARE = 1, $DPI_AWARENESS_PER_MONITOR_AWARE = 2 ;https://learn.microsoft.com/en-us/windows/win32/hidpi/dpi-awareness-context ;These are pseudo-handles: formally pointers (DECLARE_HANDLE), but the values -1..-5 are ;sentinels the API maps internally. Never dereference them. Kept as plain literals on ;purpose - deriving them from the DPI_AWARENESS enum couples two unrelated enumerations. Global Const $DPI_AWARENESS_CONTEXT_UNAWARE = -1 Global Const $DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = -2 Global Const $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = -3 Global Const $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4 Global Const $DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED = -5 ;Unified awareness levels for _WinAPI_SetDPIAwareness (version independent). ;Note the ordering is NOT "increasing awareness": GDISCALED is a variant of UNAWARE and ;deliberately sits last, so never clamp an out-of-range value to it. Global Enum $DPI_LEVEL_UNAWARE = 0, $DPI_LEVEL_SYSTEM, $DPI_LEVEL_PER_MONITOR, $DPI_LEVEL_PER_MONITOR_V2, $DPI_LEVEL_UNAWARE_GDISCALED ;Scope for _WinAPI_SetDPIAwareness Global Enum $DPI_SCOPE_PROCESS = 1, $DPI_SCOPE_THREAD = 2 ;enum PROCESS_DPI_AWARENESS (shellscalingapi.h) Global Enum $PROCESS_DPI_UNAWARE = 0, $PROCESS_SYSTEM_DPI_AWARE, $PROCESS_PER_MONITOR_DPI_AWARE ;enum _MONITOR_DPI_TYPE Global Enum $MDT_EFFECTIVE_DPI = 0, $MDT_ANGULAR_DPI, $MDT_RAW_DPI Global Const $MDT_DEFAULT = $MDT_EFFECTIVE_DPI ;Cursor creation scaling (winuser.h, guarded by NTDDI_WIN10_CO = Windows 11 21H2) Global Const $CURSOR_CREATION_SCALING_NONE = 1 Global Const $CURSOR_CREATION_SCALING_DEFAULT = 2 ;enum DPI_HOSTING_BEHAVIOR (Win10 1803+) - controls whether a window may host child windows ;with a different DPI awareness. MIXED is needed for legacy controls inside a modern window. Global Enum $DPI_HOSTING_BEHAVIOR_INVALID = -1, $DPI_HOSTING_BEHAVIOR_DEFAULT = 0, $DPI_HOSTING_BEHAVIOR_MIXED = 1 ;Windows Message Codes Global Const $WM_DPICHANGED = 0x02E0, $WM_DPICHANGED_BEFOREPARENT = 0x02E2, $WM_DPICHANGED_AFTERPARENT = 0x02E3, $WM_GETDPISCALEDSIZE = 0x02E4 ;DpiChangeBehavior Global Const $DDC_DEFAULT = 0 Global Const $DDC_DISABLE_ALL = 1 Global Const $DDC_DISABLE_RESIZE = 2 Global Const $DDC_DISABLE_CONTROL_RELAYOUT = 4 Global Const $DCDC_DEFAULT = 0 Global Const $DCDC_DISABLE_FONT_UPDATE = 1 Global Const $DCDC_DISABLE_RELAYOUT = 2 ;Internal: OS build thresholds. @OSBuild is compared against these instead of using ;hand-written Case ranges, which is where the original version left a gap. Global Const $__DPI_BUILD_VISTA = 6000 ;SetProcessDPIAware Global Const $__DPI_BUILD_WIN81 = 9600 ;SetProcessDpiAwareness, GetDpiForMonitor Global Const $__DPI_BUILD_1607 = 14393 ;SetThreadDpiAwarenessContext, GetDpiForSystem, SetProcessDpiAwarenessContext Global Const $__DPI_BUILD_1703 = 15063 ;PER_MONITOR_AWARE_V2 context value Global Const $__DPI_BUILD_1803 = 17134 ;GetDpiFromDpiAwarenessContext, InheritWindowMonitor Global Const $__DPI_BUILD_1809 = 17763 ;UNAWARE_GDISCALED Global Const $__DPI_ERROR_ACCESS_DENIED = 5 ;Win32 ERROR_ACCESS_DENIED Global Const $__DPI_E_ACCESSDENIED = 0x80070005 ;HRESULT E_ACCESSDENIED Global Const $__DPI_DEFAULT_DPI = 96 ;USER_DEFAULT_SCREEN_DPI #EndRegion DPI Constants #Region Internal helpers ;Reads the thread's last Win32 error. Must be called immediately after the DllCall whose ;failure reason is wanted - any intervening call may overwrite it. Func __WinAPI_DPI_GetLastError() Local $aResult = DllCall("kernel32.dll", "dword", "GetLastError") If @error Or Not IsArray($aResult) Then Return 0 Return $aResult[0] EndFunc ;==>__WinAPI_DPI_GetLastError ;Best available way of reading the current DPI, degrading with the OS version. ;Returns 0 if nothing worked. Func __WinAPI_DPI_QueryCurrent() Local $iDPI = 0 If @OSBuild >= $__DPI_BUILD_1607 Then $iDPI = _WinAPI_GetDpiForSystem() If @error Then $iDPI = 0 EndIf If Not $iDPI And @OSBuild >= $__DPI_BUILD_WIN81 Then $iDPI = _WinAPI_GetDpiForMonitor() If @error Then $iDPI = 0 EndIf If Not $iDPI Then $iDPI = _WinAPI_GetDPI() ;GetDeviceCaps fallback, works on every version If @error Then $iDPI = 0 EndIf Return $iDPI EndFunc ;==>__WinAPI_DPI_QueryCurrent ;Maps a unified level (0..4) to its DPI_AWARENESS_CONTEXT pseudo-handle. ;Every level is listed explicitly. Anything unexpected falls back to PER_MONITOR_AWARE_V2 and ;never to GDISCALED - an unknown value must not silently end up in an unaware mode. Func __WinAPI_DPI_LevelToContext($iLevel) Switch $iLevel Case $DPI_LEVEL_UNAWARE Return $DPI_AWARENESS_CONTEXT_UNAWARE Case $DPI_LEVEL_SYSTEM Return $DPI_AWARENESS_CONTEXT_SYSTEM_AWARE Case $DPI_LEVEL_PER_MONITOR Return $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE Case $DPI_LEVEL_UNAWARE_GDISCALED Return $DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED Case Else Return $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 EndSwitch EndFunc ;==>__WinAPI_DPI_LevelToContext ;Folds a unified level onto the three PROCESS_DPI_AWARENESS values. GDISCALED is a variant of ;UNAWARE and V2 a variant of PER_MONITOR, so both collapse onto their base level. Func __WinAPI_DPI_LevelToCoarse($iLevel) Switch $iLevel Case $DPI_LEVEL_SYSTEM Return $PROCESS_SYSTEM_DPI_AWARE Case $DPI_LEVEL_PER_MONITOR, $DPI_LEVEL_PER_MONITOR_V2 Return $PROCESS_PER_MONITOR_DPI_AWARE Case Else ;UNAWARE and UNAWARE_GDISCALED Return $PROCESS_DPI_UNAWARE EndSwitch EndFunc ;==>__WinAPI_DPI_LevelToCoarse ;Determines the DPI awareness level (0..4) that is in effect for the CALLING THREAD - which is ;the process default as long as the thread has not overridden it. There is no way around that ;restriction: asked about its own process, Windows answers with the calling thread's context, ;GetDpiAwarenessContextForProcess and GetProcessDpiAwareness included, no matter whether the ;process handle is NULL or a real one. Use the result accordingly and never as proof about ;another thread. ;Returns -1 when the level cannot be determined - that means "unknown", not "mismatch". ;@extended tells how precise the answer is: 0 = exact, 1 = PROCESS_DPI_AWARENESS granularity, ;where PER_MONITOR and V2 are indistinguishable and GDISCALED reads as UNAWARE. Compare such a ;value with __WinAPI_DPI_LevelSatisfies() instead of testing it for equality. Func __WinAPI_DPI_GetActiveLevel() Local $iContext = 0, $iLevel = 0, $iAwareness = 0 If @OSBuild >= $__DPI_BUILD_1607 Then $iContext = _WinAPI_GetThreadDpiAwarenessContext() If Not @error And $iContext Then ;The context is an opaque handle, so ask the API which of the known levels it equals For $iLevel = $DPI_LEVEL_UNAWARE To $DPI_LEVEL_UNAWARE_GDISCALED If _WinAPI_AreDpiAwarenessContextsEqual($iContext, __WinAPI_DPI_LevelToContext($iLevel)) Then Return SetError(0, 0, $iLevel) Next ;Unknown context - fall back to the coarse DPI_AWARENESS value (0..2) $iAwareness = _WinAPI_GetAwarenessFromDpiAwarenessContext($iContext) If Not @error And $iAwareness <> $DPI_AWARENESS_INVALID Then Return SetError(0, 1, $iAwareness) EndIf EndIf If @OSBuild >= $__DPI_BUILD_WIN81 Then $iAwareness = _WinAPI_GetProcessDpiAwareness() If Not @error Then Return SetError(0, 1, $iAwareness) ;PROCESS_DPI_AWARENESS 0..2 EndIf If @OSBuild >= $__DPI_BUILD_VISTA Then ;Vista..Win8 know system awareness only $iAwareness = _WinAPI_IsProcessDPIAware() If Not @error Then Return SetError(0, 1, ($iAwareness ? $DPI_LEVEL_SYSTEM : $DPI_LEVEL_UNAWARE)) EndIf Return SetError(0, 0, -1) EndFunc ;==>__WinAPI_DPI_GetActiveLevel ;Decides whether the level that is really active satisfies the request. $bCoarse is the ;precision flag of __WinAPI_DPI_GetActiveLevel(): on that granularity both sides have to be ;folded down first, otherwise a correctly applied V2 would be reported as a mismatch. ;An undetermined level (-1) counts as satisfied - guessing would only produce false alarms. Func __WinAPI_DPI_LevelSatisfies($iActiveLevel, $bCoarse, $iRequestedLevel) If $iActiveLevel = -1 Then Return True If Not $bCoarse Then Return ($iActiveLevel = $iRequestedLevel) Return (__WinAPI_DPI_LevelToCoarse($iActiveLevel) = __WinAPI_DPI_LevelToCoarse($iRequestedLevel)) EndFunc ;==>__WinAPI_DPI_LevelSatisfies #EndRegion Internal helpers #Region WinAPI DPI - queries ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-adjustwindowrectexfordpi ;The API expands an existing client RECT into a window RECT, so the rectangle is an input. ;Passing all four coordinates as 0 yields the pure frame offsets (left/top become negative). ;Returns: $tagRECT structure. @error 1 = call failed (@extended = @error), 2 = API returned FALSE. Func _WinAPI_AdjustWindowRectExForDpi($iDpi, $dwStyle, $dwExStyle = 0, $bMenu = False, $iLeft = 0, $iTop = 0, $iRight = 0, $iBottom = 0) Local $tRECT = DllStructCreate($tagRECT) $tRECT.Left = $iLeft $tRECT.Top = $iTop $tRECT.Right = $iRight $tRECT.Bottom = $iBottom ;Parameter order per MSDN: lpRect, dwStyle, bMenu, dwExStyle, dpi Local $aResult = DllCall("user32.dll", "bool", "AdjustWindowRectExForDpi", "struct*", $tRECT, "dword", $dwStyle, "bool", $bMenu, "dword", $dwExStyle, "uint", $iDpi) ;Win10 1607+ / no server support If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), 0) Return $tRECT EndFunc ;==>_WinAPI_AdjustWindowRectExForDpi ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfofordpi ;$pvParam must be a DllStruct (or pointer) matching the requested $uiAction. Func _WinAPI_SystemParametersInfoForDpi($uiAction, $uiParam, $pvParam, $fWinIni, $iDpi) Local $aResult = DllCall("user32.dll", "bool", "SystemParametersInfoForDpi", "uint", $uiAction, "uint", $uiParam, "struct*", $pvParam, "uint", $fWinIni, "uint", $iDpi) ;Win10 1607+ / no server support If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), 0) Return True EndFunc ;==>_WinAPI_SystemParametersInfoForDpi ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-inheritwindowmonitor Func _WinAPI_InheritWindowMonitor($hWnd, $hWndInherit) Local $aResult = DllCall("user32.dll", "bool", "InheritWindowMonitor", "hwnd", $hWnd, "hwnd", $hWndInherit) ;Win10 1803+ / Windows Server 2016+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), 0) Return True EndFunc ;==>_WinAPI_InheritWindowMonitor ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-isvaliddpiawarenesscontext ;A result of False means "this context is not valid" - that is an answer, not an error. ;@error is only set when the call itself could not be made. Func _WinAPI_IsValidDpiAwarenessContext($iContext) Local $aResult = DllCall("user32.dll", "bool", "IsValidDpiAwarenessContext", "int_ptr", $iContext) ;Win10 1607+ / no server support If @error Or Not IsArray($aResult) Then Return SetError(1, @error, False) Return ($aResult[0] <> 0) EndFunc ;==>_WinAPI_IsValidDpiAwarenessContext ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-logicaltophysicalpointforpermonitordpi Func _WinAPI_LogicalToPhysicalPointForPerMonitorDPI($hWnd, $iX, $iY) Local $tPOINT = DllStructCreate($tagPOINT) $tPOINT.x = $iX $tPOINT.y = $iY Local $aResult = DllCall("user32.dll", "bool", "LogicalToPhysicalPointForPerMonitorDPI", "hwnd", $hWnd, "struct*", $tPOINT) ;Win8.1+ / no server support If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), 0) Return $tPOINT EndFunc ;==>_WinAPI_LogicalToPhysicalPointForPerMonitorDPI ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-physicaltologicalpointforpermonitordpi Func _WinAPI_PhysicalToLogicalPointForPerMonitorDPI($hWnd, $iX, $iY) Local $tPOINT = DllStructCreate($tagPOINT) $tPOINT.x = $iX $tPOINT.y = $iY Local $aResult = DllCall("user32.dll", "bool", "PhysicalToLogicalPointForPerMonitorDPI", "hwnd", $hWnd, "struct*", $tPOINT) ;Win8.1+ / no server support If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), 0) Return $tPOINT EndFunc ;==>_WinAPI_PhysicalToLogicalPointForPerMonitorDPI ;GDI+ based DPI of a window (0 = desktop). Requires _GDIPlus_Startup beforehand. Func _GDIPlus_GetDPI($hGUI = 0) Local $hGfx = _GDIPlus_GraphicsCreateFromHWND($hGUI) If @error Then Return SetError(1, @error, 0) Local $aResult = DllCall($__g_hGDIPDll, "int", "GdipGetDpiX", "handle", $hGfx, "float*", 0) Local $iErr = @error _GDIPlus_GraphicsDispose($hGfx) ;always dispose, even on failure - the original leaked here If $iErr Or Not IsArray($aResult) Then Return SetError(2, $iErr, 0) If $aResult[0] Then Return SetError(3, $aResult[0], 0) ;GDI+ status, 0 = Ok Return $aResult[2] EndFunc ;==>_GDIPlus_GetDPI ;GetDeviceCaps based DPI. Works on every Windows version, but returns a virtualised value ;when the calling thread is DPI unaware. Func _WinAPI_GetDPI($hWnd = 0) If Not $hWnd Then $hWnd = _WinAPI_GetDesktopWindow() Local Const $hDC = _WinAPI_GetDC($hWnd) If @error Or Not $hDC Then Return SetError(1, 0, 0) Local Const $iDPI = _WinAPI_GetDeviceCaps($hDC, $LOGPIXELSX) Local Const $iErr = @error _WinAPI_ReleaseDC($hWnd, $hDC) If $iErr Or Not $iDPI Then Return SetError(2, $iErr, 0) Return $iDPI EndFunc ;==>_WinAPI_GetDPI ;https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getdpiformonitor ;$hMonitor = 0 auto-selects the primary monitor. ;$bBothAxes = True returns a 2-element array [dpiX, dpiY] instead of dpiX only. Func _WinAPI_GetDpiForMonitor($hMonitor = 0, $iDpiType = $MDT_DEFAULT, $bBothAxes = False) If Not $hMonitor Then Local $aMonitors = _WinAPI_EnumDisplayMonitors() If @error Or Not IsArray($aMonitors) Then Return SetError(1, @error, 0) Local $aMI For $i = 1 To $aMonitors[0][0] $aMI = _WinAPI_GetMonitorInfo($aMonitors[$i][0]) If @error Or Not IsArray($aMI) Then ContinueLoop ;_WinAPI_GetMonitorInfo already reduces the MONITORINFO flags to 0 or 1 in $aMI[2], ;BitAND keeps working should that ever change back to the raw flag field If BitAND($aMI[2], 1) Then ;MONITORINFOF_PRIMARY $hMonitor = $aMonitors[$i][0] ExitLoop EndIf Next If Not $hMonitor Then Return SetError(2, 0, 0) ;no primary monitor found EndIf Local $tDpiX = DllStructCreate("uint dpiX") Local $tDpiY = DllStructCreate("uint dpiY") Local $aResult = DllCall("Shcore.dll", "long", "GetDpiForMonitor", _ ;Win8.1+ "handle", $hMonitor, _ "long", $iDpiType, _ "struct*", $tDpiX, _ "struct*", $tDpiY) If @error Or Not IsArray($aResult) Then Return SetError(3, @error, 0) If $aResult[0] <> 0 Then Return SetError(4, $aResult[0], 0) ;HRESULT check If Not $bBothAxes Then Return $tDpiX.dpiX Local $aReturn[2] = [$tDpiX.dpiX, $tDpiY.dpiY] Return $aReturn EndFunc ;==>_WinAPI_GetDpiForMonitor ;Kept for source compatibility - always returns a 2 element array [dpiX, dpiY], on failure ;filled with 0 so that a caller indexing the result does not run into a subscript error. Func _WinAPI_GetDpiForMonitor2($hMonitor, $iDpiType = $MDT_EFFECTIVE_DPI) Local $aReturn = _WinAPI_GetDpiForMonitor($hMonitor, $iDpiType, True) Local Const $iErr = @error, $iExt = @extended If $iErr Or Not IsArray($aReturn) Then Local $aEmpty[2] = [0, 0] Return SetError($iErr, $iExt, $aEmpty) EndIf Return SetError(0, $iExt, $aReturn) EndFunc ;==>_WinAPI_GetDpiForMonitor2 ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpiforwindow Func _WinAPI_GetDpiForWindow($hWnd) Local $aResult = DllCall("user32.dll", "uint", "GetDpiForWindow", "hwnd", $hWnd) ;Win10 1607+ / no server support If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, 0, 0) ;0 = invalid hwnd Return $aResult[0] EndFunc ;==>_WinAPI_GetDpiForWindow ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpiforsystem Func _WinAPI_GetDpiForSystem() Local $aResult = DllCall("user32.dll", "uint", "GetDpiForSystem") ;Win10 1607+ / no server support If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, 0, 0) Return $aResult[0] EndFunc ;==>_WinAPI_GetDpiForSystem ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getthreaddpiawarenesscontext ;The returned handle is opaque: depending on the build it can be a real pointer or one of the ;-1..-5 pseudo-handles. Never compare it with the DPI_AWARENESS_CONTEXT_* constants directly, ;always use _WinAPI_AreDpiAwarenessContextsEqual. Func _WinAPI_GetThreadDpiAwarenessContext() Local $aResult = DllCall("user32.dll", "int_ptr", "GetThreadDpiAwarenessContext") ;Win10 1607+ / no server support If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, 0, 0) Return $aResult[0] EndFunc ;==>_WinAPI_GetThreadDpiAwarenessContext ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpifromdpiawarenesscontext ;MSDN: PER_MONITOR_AWARE and PER_MONITOR_AWARE_V2 contexts return 0 because the real DPI ;cannot be determined without an HWND. 0 is therefore NOT an error here. Func _WinAPI_GetDpiFromDpiAwarenessContext($iContext) Local $aResult = DllCall("user32.dll", "uint", "GetDpiFromDpiAwarenessContext", "int_ptr", $iContext) ;Win10 1803+ / Windows Server 2016+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) Return $aResult[0] EndFunc ;==>_WinAPI_GetDpiFromDpiAwarenessContext ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getawarenessfromdpiawarenesscontext ;Returns a DPI_AWARENESS value. 0 = DPI_AWARENESS_UNAWARE is a valid result, and the "int" ;return type keeps DPI_AWARENESS_INVALID (-1) intact instead of turning it into 4294967295. ;Note: PER_MONITOR_AWARE and PER_MONITOR_AWARE_V2 both report 2 - use ;_WinAPI_AreDpiAwarenessContextsEqual to distinguish them. Func _WinAPI_GetAwarenessFromDpiAwarenessContext($iContext) Local $aResult = DllCall("user32.dll", "int", "GetAwarenessFromDpiAwarenessContext", "int_ptr", $iContext) ;Win10 1607+ / no server support If @error Or Not IsArray($aResult) Then Return SetError(1, @error, $DPI_AWARENESS_INVALID) Return $aResult[0] EndFunc ;==>_WinAPI_GetAwarenessFromDpiAwarenessContext ;Convenience: DPI_AWARENESS of the calling thread. Func _WinAPI_GetThreadDpiAwareness() Local $iContext = _WinAPI_GetThreadDpiAwarenessContext() If @error Then Return SetError(1, @error, $DPI_AWARENESS_INVALID) Local $iAwareness = _WinAPI_GetAwarenessFromDpiAwarenessContext($iContext) If @error Then Return SetError(2, @error, $DPI_AWARENESS_INVALID) Return $iAwareness EndFunc ;==>_WinAPI_GetThreadDpiAwareness ;Convenience: does the calling thread run in the given context? Func _WinAPI_IsThreadDpiAwarenessContext($iContext) Local $iCurrent = _WinAPI_GetThreadDpiAwarenessContext() If @error Then Return SetError(1, @error, False) Local $bEqual = _WinAPI_AreDpiAwarenessContextsEqual($iCurrent, $iContext) If @error Then Return SetError(2, @error, False) Return $bEqual EndFunc ;==>_WinAPI_IsThreadDpiAwarenessContext ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdpiawarenesscontextforprocess ;$hProcess = 0 means the current process. Careful with that: asked about its own process, ;Windows returns the context of the CALLING THREAD, so a thread that overrode its awareness ;gets its own value back, not the process default. For another process the answer is the real ;process context. ;MSDN names Win10 1607 as the minimum, winuser.h however guards the API with NTDDI_WIN10_19H1, ;so only from 1903 (build 18362) on can the export be relied on. Func _WinAPI_GetDpiAwarenessContextForProcess($hProcess = 0) Local $aResult = DllCall("user32.dll", "int_ptr", "GetDpiAwarenessContextForProcess", "handle", $hProcess) ;Win10 1903+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, 0, 0) Return $aResult[0] EndFunc ;==>_WinAPI_GetDpiAwarenessContextForProcess ;https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getprocessdpiawareness ;Counterpart of _WinAPI_SetProcessDpiAwareness. $hProcess = 0 asks for the current process. ;Returns a PROCESS_DPI_AWARENESS value (0..2). PER_MONITOR_AWARE_V2 is reported as ;$PROCESS_PER_MONITOR_DPI_AWARE - this API cannot tell V1 and V2 apart. Func _WinAPI_GetProcessDpiAwareness($hProcess = 0) Local $tAwareness = DllStructCreate("int value") Local $aResult = DllCall("Shcore.dll", "long", "GetProcessDpiAwareness", "handle", $hProcess, "struct*", $tAwareness) ;Win8.1+ / Server 2012 R2+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, $DPI_AWARENESS_INVALID) If $aResult[0] <> 0 Then Return SetError(2, $aResult[0], $DPI_AWARENESS_INVALID) ;HRESULT check Return $tAwareness.value EndFunc ;==>_WinAPI_GetProcessDpiAwareness ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-isprocessdpiaware ;Counterpart of _WinAPI_SetProcessDPIAware. Legacy check: it only distinguishes "unaware" ;from "aware", so a per monitor aware process also reports True. Func _WinAPI_IsProcessDPIAware() Local $aResult = DllCall("user32.dll", "bool", "IsProcessDPIAware") ;Vista+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, False) Return ($aResult[0] <> 0) EndFunc ;==>_WinAPI_IsProcessDPIAware ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemdpiforprocess Func _WinAPI_GetSystemDpiForProcess($hProcess = 0) Local $aResult = DllCall("user32.dll", "uint", "GetSystemDpiForProcess", "handle", $hProcess) ;Win10 1803+ / Windows Server 2016+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, 0, 0) Return $aResult[0] EndFunc ;==>_WinAPI_GetSystemDpiForProcess ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowdpiawarenesscontext Func _WinAPI_GetWindowDpiAwarenessContext($hWnd) Local $aResult = DllCall("user32.dll", "int_ptr", "GetWindowDpiAwarenessContext", "hwnd", $hWnd) ;Win10 1607+ / Windows Server 2016+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, 0, 0) ;NULL = invalid window Return $aResult[0] EndFunc ;==>_WinAPI_GetWindowDpiAwarenessContext ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getthreaddpihostingbehavior ;Returns a DPI_HOSTING_BEHAVIOR value. $DPI_HOSTING_BEHAVIOR_DEFAULT (0) is a valid result. Func _WinAPI_GetThreadDpiHostingBehavior() Local $aResult = DllCall("user32.dll", "int", "GetThreadDpiHostingBehavior") ;Win10 1803+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, $DPI_HOSTING_BEHAVIOR_INVALID) Return $aResult[0] EndFunc ;==>_WinAPI_GetThreadDpiHostingBehavior ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowdpihostingbehavior ;Returns a DPI_HOSTING_BEHAVIOR value, $DPI_HOSTING_BEHAVIOR_INVALID (-1) for an invalid window. Func _WinAPI_GetWindowDpiHostingBehavior($hWnd) Local $aResult = DllCall("user32.dll", "int", "GetWindowDpiHostingBehavior", "hwnd", $hWnd) ;Win10 1803+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, $DPI_HOSTING_BEHAVIOR_INVALID) If $aResult[0] = $DPI_HOSTING_BEHAVIOR_INVALID Then Return SetError(2, 0, $DPI_HOSTING_BEHAVIOR_INVALID) Return $aResult[0] EndFunc ;==>_WinAPI_GetWindowDpiHostingBehavior ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-aredpiawarenesscontextsequal ;False is a valid answer, so @error is only set when the call itself failed. Func _WinAPI_AreDpiAwarenessContextsEqual($iContextA, $iContextB) Local $aResult = DllCall("user32.dll", "bool", "AreDpiAwarenessContextsEqual", "int_ptr", $iContextA, "int_ptr", $iContextB) ;Win10 1607+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, False) Return ($aResult[0] <> 0) EndFunc ;==>_WinAPI_AreDpiAwarenessContextsEqual ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetricsfordpi ;0 can be a legitimate metric value, so it is not treated as an error. Func _WinAPI_GetSystemMetricsForDpi($nIndex, $iDpi) Local $aResult = DllCall("user32.dll", "int", "GetSystemMetricsForDpi", "int", $nIndex, "uint", $iDpi) ;Win10 1607+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) Return $aResult[0] EndFunc ;==>_WinAPI_GetSystemMetricsForDpi ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdialogdpichangebehavior ;Returns a DDC_* bit mask. $DDC_DEFAULT (0) is a valid result. Func _WinAPI_GetDialogDpiChangeBehavior($hWnd) Local $aResult = DllCall("user32.dll", "int", "GetDialogDpiChangeBehavior", "hwnd", $hWnd) ;Win10 1703+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, -1) Return $aResult[0] EndFunc ;==>_WinAPI_GetDialogDpiChangeBehavior ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdialogcontroldpichangebehavior ;Returns a DCDC_* bit mask. $DCDC_DEFAULT (0) is a valid result. Func _WinAPI_GetDialogControlDpiChangeBehavior($hWnd) Local $aResult = DllCall("user32.dll", "int", "GetDialogControlDpiChangeBehavior", "hwnd", $hWnd) ;Win10 1703+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, -1) Return $aResult[0] EndFunc ;==>_WinAPI_GetDialogControlDpiChangeBehavior #EndRegion WinAPI DPI - queries #Region WinAPI DPI - setters ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setprocessdpiawarenesscontext ;@extended carries GetLastError() on failure - ERROR_ACCESS_DENIED (5) means the awareness ;was already fixed (typically by the application manifest) and cannot be changed. Func _WinAPI_SetProcessDpiAwarenessContext($iContext) Local $aResult = DllCall("user32.dll", "bool", "SetProcessDpiAwarenessContext", "int_ptr", $iContext) ;Win10 1703+ / Windows Server 2016+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, False) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), False) Return True EndFunc ;==>_WinAPI_SetProcessDpiAwarenessContext ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setthreaddpiawarenesscontext ;Returns the PREVIOUS context - keep it and restore it when you are done, otherwise you ;change the behaviour of unrelated code running on the same thread. Func _WinAPI_SetThreadDpiAwarenessContext($iContext) Local $aResult = DllCall("user32.dll", "int_ptr", "SetThreadDpiAwarenessContext", "int_ptr", $iContext) ;Win10 1607+ / Windows Server 2016+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), 0) ;NULL = invalid context Return $aResult[0] EndFunc ;==>_WinAPI_SetThreadDpiAwarenessContext ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setthreadcursorcreationscaling ;Decides for which DPI cursors created by this thread are scaled: $CURSOR_CREATION_SCALING_NONE ;switches the scaling off, $CURSOR_CREATION_SCALING_DEFAULT restores the system behaviour, any ;larger value is used as the target DPI. Only affects cursors created afterwards, so set it, ;create them, restore the previous value that this function returns. ;winuser.h guards the API with NTDDI_WIN10_CO - Windows 11 21H2 is the first version with it. Func _WinAPI_SetThreadCursorCreationScaling($iCursorDpi = $CURSOR_CREATION_SCALING_DEFAULT) Local $aResult = DllCall("user32.dll", "uint", "SetThreadCursorCreationScaling", "uint", $iCursorDpi) ;Win11 21H2+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), 0) ;0 = call failed Return $aResult[0] EndFunc ;==>_WinAPI_SetThreadCursorCreationScaling ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setthreaddpihostingbehavior ;Decides whether windows created by this thread may host child windows with a different DPI ;awareness ($DPI_HOSTING_BEHAVIOR_MIXED) or not ($DPI_HOSTING_BEHAVIOR_DEFAULT). ;Returns the PREVIOUS behaviour - restore it once the windows in question have been created. Func _WinAPI_SetThreadDpiHostingBehavior($iBehavior) Local $aResult = DllCall("user32.dll", "int", "SetThreadDpiHostingBehavior", "int", $iBehavior) ;Win10 1803+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, $DPI_HOSTING_BEHAVIOR_INVALID) If $aResult[0] = $DPI_HOSTING_BEHAVIOR_INVALID Then Return SetError(2, 0, $DPI_HOSTING_BEHAVIOR_INVALID) Return $aResult[0] EndFunc ;==>_WinAPI_SetThreadDpiHostingBehavior ;https://learn.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-setprocessdpiawareness ;$iAwareness is a PROCESS_DPI_AWARENESS value (0..2), not a DPI_AWARENESS_CONTEXT. ;@extended carries the HRESULT - E_ACCESSDENIED (0x80070005) means "already set". Func _WinAPI_SetProcessDpiAwareness($iAwareness = $PROCESS_PER_MONITOR_DPI_AWARE) Local $aResult = DllCall("Shcore.dll", "long", "SetProcessDpiAwareness", "int", $iAwareness) ;Win8.1+ / Server 2012 R2+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, False) If $aResult[0] <> 0 Then Return SetError(2, $aResult[0], False) Return True EndFunc ;==>_WinAPI_SetProcessDpiAwareness ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setprocessdpiaware Func _WinAPI_SetProcessDPIAware() Local $aResult = DllCall("user32.dll", "bool", "SetProcessDPIAware") ;Vista+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, False) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), False) Return True EndFunc ;==>_WinAPI_SetProcessDPIAware ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enablenonclientdpiscaling ;Unnecessary in PER_MONITOR_AWARE_V2 contexts - V2 scales the non-client area already. Func _WinAPI_EnableNonClientDpiScaling($hWnd) Local $aResult = DllCall("user32.dll", "bool", "EnableNonClientDpiScaling", "hwnd", $hWnd) ;Win10 1607+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, False) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), False) Return True EndFunc ;==>_WinAPI_EnableNonClientDpiScaling ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setdialogdpichangebehavior Func _WinAPI_SetDialogDpiChangeBehavior($hWnd, $iMask, $iValues) Local $aResult = DllCall("user32.dll", "bool", "SetDialogDpiChangeBehavior", "hwnd", $hWnd, "int", $iMask, "int", $iValues) ;Win10 1703+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, False) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), False) Return True EndFunc ;==>_WinAPI_SetDialogDpiChangeBehavior ;https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setdialogcontroldpichangebehavior Func _WinAPI_SetDialogControlDpiChangeBehavior($hWnd, $iMask, $iValues) Local $aResult = DllCall("user32.dll", "bool", "SetDialogControlDpiChangeBehavior", "hwnd", $hWnd, "int", $iMask, "int", $iValues) ;Win10 1703+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, False) If Not $aResult[0] Then Return SetError(2, __WinAPI_DPI_GetLastError(), False) Return True EndFunc ;==>_WinAPI_SetDialogControlDpiChangeBehavior ;https://learn.microsoft.com/en-us/windows/win32/api/uxtheme/nf-uxtheme-openthemedatafordpi ;The caller owns the returned HTHEME and must release it with _WinAPI_CloseThemeData. Func _WinAPI_OpenThemeDataForDpi($hWnd, $pszClassList, $iDpi) Local $aResult = DllCall("uxtheme.dll", "handle", "OpenThemeDataForDpi", "hwnd", $hWnd, "wstr", $pszClassList, "uint", $iDpi) ;Win10 1703+ If @error Or Not IsArray($aResult) Then Return SetError(1, @error, 0) If Not $aResult[0] Then Return SetError(2, 0, 0) Return $aResult[0] EndFunc ;==>_WinAPI_OpenThemeDataForDpi #EndRegion WinAPI DPI - setters #Region High level ; #FUNCTION# =================================================================================== ; Name ..........: _WinAPI_SetDPIAwareness ; Description ...: Sets the DPI awareness using the best method the running OS supports. ; Syntax ........: _WinAPI_SetDPIAwareness([$iAwarenessLevel = $DPI_LEVEL_PER_MONITOR[, $iMode = $DPI_SCOPE_PROCESS]]) ; Parameters ....: $iAwarenessLevel - unified level, one of $DPI_LEVEL_*: ; 0 = UNAWARE ; 1 = SYSTEM ; 2 = PER_MONITOR (default) ; 3 = PER_MONITOR_V2 (Win10 1703+) ; 4 = UNAWARE_GDISCALED (Win10 1809+) ; Raw DPI_AWARENESS_CONTEXT values (-1..-5) are also ; accepted for backwards compatibility. ; $iMode - $DPI_SCOPE_PROCESS (1, default) or $DPI_SCOPE_THREAD (2) ; Return values .: Success: the current DPI. @extended = 1 means the DPI could not be ; determined and the default of 96 was returned instead. ; Failure: 0 and @error set: ; 1 - no method available or all methods failed (@extended = last Win32 error) ; 10 - thread scope requested but the OS is older than Win10 1607 ; 11 - SetThreadDpiAwarenessContext failed (@extended = last Win32 error) ; Mismatch: the current DPI is returned and @error is set to ; 12 - the awareness in effect differs from the requested one, either ; because the application manifest already fixed it or because ; only an older API was available, which cannot reach every ; level. @extended holds the $DPI_LEVEL_* value that is really ; active. Process scope only - see the remarks. ; Remarks .......: Call this before the first GUICreate or any other window access. ; Levels above the OS capability are silently downgraded: V2 -> PER_MONITOR, ; GDISCALED -> UNAWARE. That downgrade is expected and does NOT raise @error 12. ; The result is verified afterwards, so a manifest that pins a different ; awareness, and a fallback that only reached a lower level, both surface as ; @error 12 instead of being reported as plain success. Which level a ; successful step established is known without asking; only an awareness that ; turned out to be immutable has to be queried, and that query answers for the ; calling thread, because Windows reports nothing else about its own process. ; A thread that overrode its own awareness beforehand can therefore turn that ; one case into a mismatch report. On systems where the active level cannot be ; determined at all the check is skipped. ; In thread scope the previous context is NOT returned. If you need to ; restore it, call _WinAPI_SetThreadDpiAwarenessContext directly. ; Setting the awareness via the application manifest is more robust than ; calling this function, because windows can in principle be created before ; the first script statement runs. ; ============================================================================================== Func _WinAPI_SetDPIAwareness($iAwarenessLevel = $DPI_LEVEL_PER_MONITOR, $iMode = $DPI_SCOPE_PROCESS) Local $iActiveLevel = -1, $iDPI = 0, $iThreadDPI = 0, $iFlags = 0 ;--- Normalise the requested level --------------------------------------------------- ;Accept raw DPI_AWARENESS_CONTEXT values: -1 -> 0, -2 -> 1, ... -5 -> 4 If $iAwarenessLevel < 0 Then $iAwarenessLevel = (-$iAwarenessLevel) - 1 ;Int() first: a fractional value would pass both clamps below and then hit the fallback ;branch of the context map $iAwarenessLevel = Int($iAwarenessLevel) If $iAwarenessLevel < $DPI_LEVEL_UNAWARE Then $iAwarenessLevel = $DPI_LEVEL_UNAWARE ;Clamp to V2, NOT to GDISCALED: an out-of-range value must not silently select an ;unaware mode. This also closes the out-of-bounds crash the old context map had. If $iAwarenessLevel > $DPI_LEVEL_UNAWARE_GDISCALED Then $iAwarenessLevel = $DPI_LEVEL_PER_MONITOR_V2 ;--- Downgrade to what this OS actually supports -------------------------------------- If $iAwarenessLevel = $DPI_LEVEL_PER_MONITOR_V2 And @OSBuild < $__DPI_BUILD_1703 Then $iAwarenessLevel = $DPI_LEVEL_PER_MONITOR ;GDISCALED is an UNAWARE variant, so its fallback is UNAWARE - not PER_MONITOR If $iAwarenessLevel = $DPI_LEVEL_UNAWARE_GDISCALED And @OSBuild < $__DPI_BUILD_1809 Then $iAwarenessLevel = $DPI_LEVEL_UNAWARE Local Const $iContext = __WinAPI_DPI_LevelToContext($iAwarenessLevel) If $iMode <> $DPI_SCOPE_THREAD Then $iMode = $DPI_SCOPE_PROCESS ;--- Thread scope --------------------------------------------------------------------- If $iMode = $DPI_SCOPE_THREAD Then ;There is no legacy equivalent - per-thread awareness starts with Win10 1607 If @OSBuild < $__DPI_BUILD_1607 Then Return SetError(10, 0, 0) _WinAPI_SetThreadDpiAwarenessContext($iContext) ;@extended of the wrapper carries the Win32 error, @error only says which check failed If @error Then Return SetError(11, @extended, 0) $iThreadDPI = __WinAPI_DPI_QueryCurrent() If Not $iThreadDPI Then $iThreadDPI = $__DPI_DEFAULT_DPI $iFlags = 1 EndIf ;No verification needed: SetThreadDpiAwarenessContext applies the context as given or ;fails outright, which the check above already turned into @error 11. Return SetError(0, $iFlags, $iThreadDPI) EndIf ;--- Process scope: descending fallback chain ----------------------------------------- ;$iAchievedLevel keeps what the successful step really established. It stays -1 only when ;the awareness turned out to be immutable, which is the one case that has to be queried. Local $bDone = False, $iLastError = 0, $iAchievedLevel = -1, $iPrecision = 0 ;Step 1: SetProcessDpiAwarenessContext - the only route to V2 / GDISCALED. MSDN gives 1607 ;as the minimum, so it is tried from there on; the context VALUES for V2 and GDISCALED need ;1703 / 1809, which the downgrade above already took care of. Should the export be missing ;on an early build, the call simply fails and step 2 takes over. If @OSBuild >= $__DPI_BUILD_1607 Then If _WinAPI_SetProcessDpiAwarenessContext($iContext) Then $bDone = True $iAchievedLevel = $iAwarenessLevel ;the context was applied exactly as passed Else $iLastError = @extended ;ERROR_ACCESS_DENIED: the awareness is already fixed (manifest) and immutable. ;Do not fail here - whether the fixed state matches the request is decided by the ;verification at the end of this function. If $iLastError = $__DPI_ERROR_ACCESS_DENIED Then $bDone = True EndIf EndIf ;Step 2: SetProcessDpiAwareness - Win8.1+, knows only three levels If Not $bDone And @OSBuild >= $__DPI_BUILD_WIN81 Then ;PER_MONITOR and V2 both land on PROCESS_PER_MONITOR_DPI_AWARE, GDISCALED on UNAWARE Local $iLegacy = __WinAPI_DPI_LevelToCoarse($iAwarenessLevel) If _WinAPI_SetProcessDpiAwareness($iLegacy) Then $bDone = True ;This API cannot deliver V2, so a V2 request ends up one level lower here $iAchievedLevel = $iLegacy ;PROCESS_DPI_AWARENESS 0..2 = the first three levels Else $iLastError = @extended If $iLastError = $__DPI_E_ACCESSDENIED Then $bDone = True ;already set EndIf EndIf ;Step 3: SetProcessDPIAware - Vista+, system aware only. Pointless for unaware levels. If Not $bDone And @OSBuild >= $__DPI_BUILD_VISTA And $iAwarenessLevel <> $DPI_LEVEL_UNAWARE And $iAwarenessLevel <> $DPI_LEVEL_UNAWARE_GDISCALED Then If _WinAPI_SetProcessDPIAware() Then $bDone = True $iAchievedLevel = $DPI_LEVEL_SYSTEM ;this API knows nothing above system awareness Else $iLastError = @extended If $iLastError = $__DPI_ERROR_ACCESS_DENIED Then $bDone = True ;already set EndIf EndIf ;Step 4: unaware is the default state of every process - nothing to call, nothing failed. ;GDI scaling however cannot be switched on this way, so only plain UNAWARE is reached here. If Not $bDone And ($iAwarenessLevel = $DPI_LEVEL_UNAWARE Or $iAwarenessLevel = $DPI_LEVEL_UNAWARE_GDISCALED) Then $bDone = True $iAchievedLevel = $DPI_LEVEL_UNAWARE EndIf If Not $bDone Then Return SetError(1, $iLastError, 0) ;--- Report the resulting DPI --------------------------------------------------------- $iDPI = __WinAPI_DPI_QueryCurrent() ;Awareness was set successfully, so a failed DPI query is not a hard error - flag it ;in @extended and hand back the 96 DPI default. If Not $iDPI Then $iDPI = $__DPI_DEFAULT_DPI $iFlags = 1 EndIf ;--- Verify what is really in effect --------------------------------------------------- ;Two cases end up here without the request having been honoured: the fallback chain reached ;a lower level than asked for - SetProcessDPIAware can only deliver system awareness - and ;the awareness was already fixed (ACCESS_DENIED above, typically by the manifest). Both used ;to be reported as plain success, which hides wrong scaling on multi monitor setups. ;The first case is known from the step that succeeded, only the second one has to be asked ;about - and only there does the thread relative answer of __WinAPI_DPI_GetActiveLevel() ;come into play. $iActiveLevel = $iAchievedLevel If $iActiveLevel = -1 Then $iActiveLevel = __WinAPI_DPI_GetActiveLevel() $iPrecision = @extended EndIf If Not __WinAPI_DPI_LevelSatisfies($iActiveLevel, $iPrecision, $iAwarenessLevel) Then Return SetError(12, $iActiveLevel, $iDPI) Return SetError(0, $iFlags, $iDPI) EndFunc ;==>_WinAPI_SetDPIAwareness #EndRegion High level Example: expandcollapse popup;Coded by UEZ build 2026-08-26 beta ;Compile the script and run the EXE. Running it uncompiled inherits the manifest of ;AutoIt3.exe, which already fixes the DPI awareness, so the runtime call below cannot ;take effect any more. #AutoIt3Wrapper_Res_HiDpi=n ;must stay off - see the note at the top #AutoIt3Wrapper_UseX64=n #AutoIt3Wrapper_Change2CUI=y #include <GUIConstantsEx.au3> #include <MsgBoxConstants.au3> #include <WindowsConstants.au3> #include "_WinAPI_DPI.au3" Global Const $LOGICAL_DPI = 96 ;USER_DEFAULT_SCREEN_DPI, the baseline of all layout values ;Main window controls: ctrl id, logical x, y, w, h, logical font size Global $g_aMainCtrl[5][6] = [[0, 16, 16, 40, 21, 10], _ [0, 64, 16, 40, 21, 10], _ [0, 112, 16, 40, 21, 10], _ [0, 160, 16, 137, 22, 10], _ [0, 16, 48, 283, 65, 16]] ;Child window controls, same layout: id, x, y, w, h, font size (0 = no font handling) Global $g_aChildCtrl[2][6] = [[0, 16, 16, 288, 168, 65], _ [0, 0, 190, 68, 71, 0]] Global Const $MAIN_W = 314, $MAIN_H = 130 ;logical client size of the main window Global Const $CHILD_W = 320, $CHILD_H = 260 ;logical client size of the child window ;The system DPI is frozen at process start. GDI converts point sizes to pixels with THIS ;value, not with the DPI of the monitor a window currently sits on - so font sizes need ;the correction factor windowDPI / systemDPI, while pixel values need windowDPI / 96. Global $g_iSystemDPI = $LOGICAL_DPI Global $hGUI, $hGUI_child, $sImage Example1() Func Example1() ;-1..-5 are pseudo handles, not real pointers - see _WinAPI_DPI.au3 Local $iDPI = _WinAPI_SetDPIAwareness($DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) Local $iErr = @error, $iActiveLevel = @extended ;@error 12 is not fatal: the awareness is fixed - usually by a manifest - and differs from ;the request, but the returned DPI is still valid, so only the layout hint below applies. If ($iErr And $iErr <> 12) Or Not $iDPI Then Exit MsgBox($MB_ICONERROR, "ERROR", "Cannot set DPI awareness!", 10) $g_iSystemDPI = $iDPI ConsoleWrite("System DPI: " & $iDPI & @CRLF) ;Only V2 scales the non client area (title bar, borders) automatically, so it is worth ;knowing when something else is active. _WinAPI_IsThreadDpiAwarenessContext() answers the ;same question for any context at any later point. If $iErr = 12 Then ConsoleWrite("! PER_MONITOR_AWARE_V2 is NOT active, level " & $iActiveLevel & " is - check the manifest settings" & @CRLF) EndIf $sImage = _FindMerlin() If Not $sImage Then ConsoleWrite("! Merlin.gif not found, the picture control is skipped" & @CRLF) Local $iRatio = $iDPI / $LOGICAL_DPI ;--- main window --------------------------------------------------------------------- $hGUI = GUICreate("Example 1", $MAIN_W * $iRatio, $MAIN_H * $iRatio, -1, 10) ;no ratio here - point sizes are already scaled by GDI via the system DPI GUISetFont(12, 400, 0, "Times New Roman") Local $i For $i = 0 To 2 $g_aMainCtrl[$i][0] = GUICtrlCreateLabel("Label" & ($i + 1), 0, 0, 10, 10) GUICtrlSetBkColor(-1, 0x3399FF) GUICtrlSetResizing(-1, $GUI_DOCKAUTO) Next $g_aMainCtrl[3][0] = GUICtrlCreateInput("Input1", 0, 0, 10, 10) GUICtrlSetResizing(-1, $GUI_DOCKAUTO) $g_aMainCtrl[4][0] = GUICtrlCreateButton("Close", 0, 0, 10, 10) GUICtrlSetResizing(-1, $GUI_DOCKAUTO) ;--- child window -------------------------------------------------------------------- $hGUI_child = GUICreate("Child", $CHILD_W * $iRatio, $CHILD_H * $iRatio, -1, -1, -1, -1, $hGUI) GUISetBkColor(0xFFFFFF) $g_aChildCtrl[0][0] = GUICtrlCreateLabel("Label11", 0, 0, 10, 10) If $sImage Then $g_aChildCtrl[1][0] = GUICtrlCreatePic($sImage, 0, 0, 10, 10) ;The windows may well open on a monitor whose DPI differs from the system DPI, so lay ;the controls out from the DPI each window actually has instead of the initial ratio. _ApplyDpi($hGUI, _GetWindowDpi($hGUI), True) _ApplyDpi($hGUI_child, _GetWindowDpi($hGUI_child), True) ;register before GUISetState, otherwise a DPI change triggered while showing is lost GUIRegisterMsg($WM_DPICHANGED, "WM_DPICHANGED") GUISetState(@SW_SHOW, $hGUI) GUISetState(@SW_SHOW, $hGUI_child) While True Switch GUIGetMsg() Case $GUI_EVENT_CLOSE, $g_aMainCtrl[4][0] GUIDelete($hGUI_child) GUIDelete($hGUI) ExitLoop EndSwitch WEnd EndFunc ;==>Example1 ;Applies the layout of one window for the given DPI. All values are recomputed from the ;logical tables, never from the currently visible (already scaled) values. ;$bResizeWindow = True also adjusts the window itself - during WM_DPICHANGED Windows has ;already supplied the target rectangle, so it must stay False there. Func _ApplyDpi($hWnd, $iDPI, $bResizeWindow = False) Local $i ;pixel values are logical 96 DPI values and scale with windowDPI / 96 ... Local $nRatio = $iDPI / $LOGICAL_DPI ;... point sizes are converted by GDI with the frozen system DPI, so they only need the ;difference between that and the DPI of the monitor this window is currently on Local $nFontRatio = $iDPI / $g_iSystemDPI Switch $hWnd Case $hGUI If $bResizeWindow Then _ResizeClientArea($hWnd, $MAIN_W * $nRatio, $MAIN_H * $nRatio) For $i = 0 To UBound($g_aMainCtrl) - 1 _ApplyCtrl($g_aMainCtrl, $i, $nRatio, $nFontRatio) Next Case $hGUI_child If $bResizeWindow Then _ResizeClientArea($hWnd, $CHILD_W * $nRatio, $CHILD_H * $nRatio) For $i = 0 To UBound($g_aChildCtrl) - 1 _ApplyCtrl($g_aChildCtrl, $i, $nRatio, $nFontRatio) Next ;a picture control does not rescale its bitmap on its own - reassign the image If $g_aChildCtrl[1][0] Then GUICtrlSetImage($g_aChildCtrl[1][0], $sImage) EndSwitch EndFunc ;==>_ApplyDpi ;Positions and sizes a single control from its logical values. Func _ApplyCtrl(ByRef $aCtrl, $iRow, $nRatio, $nFontRatio) If Not $aCtrl[$iRow][0] Then Return ;control was not created (missing image) GUICtrlSetPos($aCtrl[$iRow][0], Round($aCtrl[$iRow][1] * $nRatio), _ Round($aCtrl[$iRow][2] * $nRatio), _ Round($aCtrl[$iRow][3] * $nRatio), _ Round($aCtrl[$iRow][4] * $nRatio)) ;$nFontRatio, NOT $nRatio - multiplying by the full DPI ratio here scales the font twice If $aCtrl[$iRow][5] Then GUICtrlSetFont($aCtrl[$iRow][0], $aCtrl[$iRow][5] * $nFontRatio, 400, 0, "Times New Roman", 5) EndFunc ;==>_ApplyCtrl ;Sets the CLIENT area to the given size, keeping the current position. Func _ResizeClientArea($hWnd, $iClientW, $iClientH) Local $aWin = WinGetPos($hWnd) If @error Or Not IsArray($aWin) Then Return Local $aClient = WinGetClientSize($hWnd) If @error Or Not IsArray($aClient) Then Return ;difference between window and client size = the non client frame at the current DPI WinMove($hWnd, "", $aWin[0], $aWin[1], Round($iClientW) + ($aWin[2] - $aClient[0]), Round($iClientH) + ($aWin[3] - $aClient[1])) EndFunc ;==>_ResizeClientArea ;DPI of a window with a fallback - a failed call must never yield 0, that would produce ;a font size of 0 further down the line. Func _GetWindowDpi($hWnd) Local $iDPI = _WinAPI_GetDpiForWindow($hWnd) ;Win10 1607+ If @error Or Not $iDPI Then $iDPI = _WinAPI_GetDpiForMonitor() ;Win8.1+ If @error Or Not $iDPI Then $iDPI = $LOGICAL_DPI EndIf Return $iDPI EndFunc ;==>_GetWindowDpi ;Looks for Merlin.gif in the usual places. Returns "" when nothing was found. Func _FindMerlin() Local $sSub = "Examples\GUI\Merlin.gif" Local $sAutoItDir = StringLeft(@AutoItExe, StringInStr(@AutoItExe, "\", 0, -1)) Local $aCandidates[4] = [@ScriptDir & "\Merlin.gif", _ $sAutoItDir & $sSub, _ @ProgramFilesDir & "\AutoIt3\" & $sSub, _ EnvGet("ProgramFiles(x86)") & "\AutoIt3\" & $sSub] Local $i For $i = 0 To UBound($aCandidates) - 1 If $aCandidates[$i] And FileExists($aCandidates[$i]) Then Return $aCandidates[$i] Next Return "" EndFunc ;==>_FindMerlin ;https://learn.microsoft.com/en-us/windows/win32/hidpi/wm-dpichanged ;wParam: LOWORD = new X DPI, HIWORD = new Y DPI ;lParam: pointer to the suggested new window RECT, already including the non client frame Func WM_DPICHANGED($hWnd, $iMsg, $wParam, $lParam) #forceref $iMsg Local $iNewDPI = _WinAPI_LoWord($wParam) Local $tRECT = DllStructCreate($tagRECT, $lParam) ;Apply the suggested rectangle as is - no AdjustWindowRectExForDpi needed here _WinAPI_SetWindowPos($hWnd, 0, $tRECT.Left, $tRECT.Top, $tRECT.Right - $tRECT.Left, $tRECT.Bottom - $tRECT.Top, BitOR($SWP_NOZORDER, $SWP_NOACTIVATE)) ;Only the controls of THIS window are touched - the old version also ran the child ;window code when the parent received the message _ApplyDpi($hWnd, $iNewDPI) $tRECT = 0 ConsoleWrite("DPI change to " & $iNewDPI & " applied to " & (($hWnd = $hGUI) ? "main" : "child") & " window" & @CRLF) Return 0 ;0 = handled, as the API expects EndFunc ;==>WM_DPICHANGED Claude added the comments and fixed some bugs. Please test if everything is working as expected, and feel free to leave comments. Edited August 26 by UEZ Code update funkey, ioa747, mLipok and 6 others 7 2 Please don't send me any personal message and ask for support! I will not reply! Selection of finest graphical examples at Codepen.io The own fart smells best! ✌Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!¯\_(ツ)_/¯ ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ
Shark007 Posted August 13 Posted August 13 (edited) @UEZ It is working well for me as a direct replacement for the previous release dated Aug 15th, '23 EDIT: I had a look at the changes from 2026-08-12 beta to 2026-08-26 beta - That was quite the clean up. I'm impressed. Thank you UEZ and also thanks to the others that contributed to this comprehensive release. Edited August 28 by Shark007 WildByDesign 1
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now