Jump to content

Recommended Posts

Posted

I think the _WinAPI_DPI UDF is now complete enough to be released here.

;Coded by UEZ build 2026-08-12 beta

#include-once
#include <GDIPlus.au3>
#include <StructureConstants.au3>
#include <WinAPIGdi.au3>
#include <WinAPISysWin.au3>
#include <WinAPIsysinfoConstants.au3> 

#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

;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, ...
Global Const $__DPI_BUILD_1703 = 15063     ;SetProcessDpiAwarenessContext, PER_MONITOR_AWARE_V2
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.
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_PER_MONITOR_V2
            Return $DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
        Case Else
            Return $DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED
    EndSwitch
EndFunc   ;==>__WinAPI_DPI_LevelToContext
#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
            ;$aMI[2] is a flags field - test the bit, do not compare for equality
            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 - returns [dpiX, dpiY].
Func _WinAPI_GetDpiForMonitor2($hMonitor, $iDpiType = $MDT_EFFECTIVE_DPI)
    Local $aReturn = _WinAPI_GetDpiForMonitor($hMonitor, $iDpiType, True)
    Return SetError(@error, @extended, $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
;Returns a REAL context handle, never one of the -1..-5 sentinels. Do not compare it with
;the DPI_AWARENESS_CONTEXT_* constants directly - 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
Func _WinAPI_GetDpiAwarenessContextForProcess($hProcess = 0)
    Local $aResult = DllCall("user32.dll", "int_ptr", "GetDpiAwarenessContextForProcess", "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_GetDpiAwarenessContextForProcess

;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-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", _   ;Win10 1607+
            "int_ptr", $iContextA, _
            "int_ptr", $iContextB)
    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/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 = @error of the wrapper)
; 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.
;                  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)
    ;--- Normalise the requested level ---------------------------------------------------
    ;Accept raw DPI_AWARENESS_CONTEXT values: -1 -> 0, -2 -> 1, ... -5 -> 4
    If $iAwarenessLevel < 0 Then $iAwarenessLevel = (-$iAwarenessLevel) - 1
    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)
        If @error Then Return SetError(11, @error, 0)
        Local $iThreadDPI = __WinAPI_DPI_QueryCurrent()
        If Not $iThreadDPI Then Return SetError(0, 1, $__DPI_DEFAULT_DPI)
        Return $iThreadDPI
    EndIf

    ;--- Process scope: descending fallback chain -----------------------------------------
    Local $bDone = False, $iLastError = 0

    ;Step 1: SetProcessDpiAwarenessContext - Win10 1703+, the only route to V2 / GDISCALED
    If @OSBuild >= $__DPI_BUILD_1703 Then
        If _WinAPI_SetProcessDpiAwarenessContext($iContext) Then
            $bDone = True
        Else
            $iLastError = @extended
            ;ERROR_ACCESS_DENIED: the awareness is already fixed (manifest) and immutable.
            ;The desired state may well be active already, so accept it instead of failing.
            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
        Local $iLegacy
        Switch $iAwarenessLevel
            Case $DPI_LEVEL_UNAWARE, $DPI_LEVEL_UNAWARE_GDISCALED
                $iLegacy = $PROCESS_DPI_UNAWARE
            Case $DPI_LEVEL_SYSTEM
                $iLegacy = $PROCESS_SYSTEM_DPI_AWARE
            Case Else
                $iLegacy = $PROCESS_PER_MONITOR_DPI_AWARE   ;PER_MONITOR and V2 both land here
        EndSwitch
        If _WinAPI_SetProcessDpiAwareness($iLegacy) Then
            $bDone = True
        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
        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
    If Not $bDone And ($iAwarenessLevel = $DPI_LEVEL_UNAWARE Or $iAwarenessLevel = $DPI_LEVEL_UNAWARE_GDISCALED) Then $bDone = True

    If Not $bDone Then Return SetError(1, $iLastError, 0)

    ;--- Report the resulting DPI ---------------------------------------------------------
    Local $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 Return SetError(0, 1, $__DPI_DEFAULT_DPI)
    Return $iDPI
EndFunc   ;==>_WinAPI_SetDPIAwareness
#EndRegion High level

 

Claude added the comments and fixed some bugs. Please test if everything is working as expected.

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!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...