Jump to content

Monitor UDF - Advanced monitor management and multi-monitor utilities.


Recommended Posts

Posted (edited)

Because working with multiple monitors was required during the development of ImageSearchUDF , this UDF was created

UDF:

#include-once
#include <WinAPIGdi.au3>
#include <WinAPISysWin.au3>
#include <WindowsConstants.au3>

; ===============================================================================================================================
; Title .........: Monitor UDF
; Description ...: Provides advanced monitor management and multi-monitor utilities.
; Author ........: Dao Van Trong - TRONG.PRO
; Version .......: 2.1
; Modified ......: Fixed bugs, added features, improved error handling, Windows XP compatibility
; Compatibility .: Windows XP SP2 and later (most functions)
;                  Windows 8.1+ required for GetDpiForMonitor API (automatic fallback on older systems)
; ===============================================================================================================================
; FUNCTIONS SUMMARY
; ===============================================================================================================================
;   _Monitor_GetList()                     - Enumerate connected monitors and fill global info.
;   _Monitor_GetCount()                    - Return total number of monitors.
;   _Monitor_GetPrimary()                  - Get index of the primary monitor.
;   _Monitor_GetInfo($iMonitor)            - Get detailed info about a monitor.
;   _Monitor_GetBounds($iMonitor, ...)     - Get full monitor rectangle (including taskbar).
;   _Monitor_GetWorkArea($iMonitor, ...)   - Get working area of a monitor (excluding taskbar).
;   _Monitor_GetDisplaySettings($iMonitor) - Get current display mode.
;   _Monitor_GetResolution($iMonitor)      - Get monitor resolution.
;   _Monitor_GetFromPoint([$x, $y])        - Get monitor from screen point or mouse.
;   _Monitor_GetFromWindow($hWnd)          - Get monitor containing a specific window.
;   _Monitor_GetFromRect(...)              - Get monitor overlapping a given rectangle.
;   _Monitor_GetVirtualBounds()            - Get bounding rectangle of the entire virtual screen.
;   _Monitor_ToVirtual($iMonitor, $x, $y)  - Convert local monitor coordinates to virtual coordinates.
;   _Monitor_FromVirtual($iMonitor, $x, $y)- Convert from virtual to local monitor coordinates.
;   _Monitor_IsVisibleWindow($hWnd)        - Check if a window is top-level visible.
;   _Monitor_MoveWindowToScreen(...)       - Move a window to specific monitor (center if unspecified).
;   _Monitor_MoveWindowToAll(...)          - Move a visible window across all monitors.
;   _Monitor_EnumAllDisplayModes($iMonitor)- Enumerate all available display modes.
;   _Monitor_ShowInfo([$bShowMsgBox = 1, $iTimeout = 10]) - Show all monitor information.
;   _Monitor_Refresh()                     - Refresh monitor list (reload from system).
;   _Monitor_IsConnected($iMonitor)       - Check if monitor is still connected.
;   _Monitor_GetDPI($iMonitor)            - Get DPI scaling for a monitor.
;   _Monitor_GetOrientation($iMonitor)     - Get display orientation (0, 90, 180, 270 degrees).
;   _Monitor_GetLayout()                  - Get current display layout configuration.
;   _Monitor_SaveLayout($sFilePath)       - Save current layout to file.
;   _Monitor_LoadLayout($sFilePath)       - Load layout from file.
;   _Monitor_GetLayoutDescription()    - Get text description of current layout.
;   _Monitor_ApplyLayoutHorizontal()     - Arrange monitors horizontally (side by side).
;   _Monitor_ApplyLayoutVertical()       - Arrange monitors vertically (stacked).
;   _Monitor_ApplyLayoutGrid([$iCols = 2[, $iRows = 2]]) - Arrange monitors in grid layout.
; ===============================================================================================================================

;~ ; Constants for SystemMetrics (define if not available, compatible with Windows XP)
;~ If Not IsDeclared("SM_CMONITORS") Then Global $SM_CMONITORS = 80
;~ If Not IsDeclared("SM_XVIRTUALSCREEN") Then Global $SM_XVIRTUALSCREEN = 76
;~ If Not IsDeclared("SM_YVIRTUALSCREEN") Then Global $SM_YVIRTUALSCREEN = 77
;~ If Not IsDeclared("SM_CXVIRTUALSCREEN") Then Global $SM_CXVIRTUALSCREEN = 78
;~ If Not IsDeclared("SM_CYVIRTUALSCREEN") Then Global $SM_CYVIRTUALSCREEN = 79

;~ ; Monitor flags (define if not available)
;~ If Not IsDeclared("MONITOR_DEFAULTTONULL") Then Global $MONITOR_DEFAULTTONULL = 0
;~ If Not IsDeclared("MONITOR_DEFAULTTOPRIMARY") Then Global $MONITOR_DEFAULTTOPRIMARY = 1
;~ If Not IsDeclared("MONITOR_DEFAULTTONEAREST") Then Global $MONITOR_DEFAULTTONEAREST = 2

;~ ; EnumDisplaySettings mode (define if not available)
;~ If Not IsDeclared("ENUM_CURRENT_SETTINGS") Then Global $ENUM_CURRENT_SETTINGS = -1
;~ If Not IsDeclared("ENUM_REGISTRY_SETTINGS") Then Global $ENUM_REGISTRY_SETTINGS = -2

;~ ; Window constants (define if not available, compatible with Windows XP)
;~ If Not IsDeclared("GWL_STYLE") Then Global $GWL_STYLE = -16
;~ If Not IsDeclared("GWL_EXSTYLE") Then Global $GWL_EXSTYLE = -20
;~ If Not IsDeclared("WS_VISIBLE") Then Global $WS_VISIBLE = 0x10000000
;~ If Not IsDeclared("WS_CHILD") Then Global $WS_CHILD = 0x40000000
;~ If Not IsDeclared("WS_EX_TOOLWINDOW") Then Global $WS_EX_TOOLWINDOW = 0x00000080

#Region --- Global Variables ---
; ===============================================================================================================================
; Global Monitor Information Array
; ===============================================================================================================================
; $__g_aMonitorList[][] structure:
;
;   [0][0] = Number of monitors detected
;   [0][1] = Virtual desktop Left coordinate (combined area)
;   [0][2] = Virtual desktop Top coordinate
;   [0][3] = Virtual desktop Right coordinate
;   [0][4] = Virtual desktop Bottom coordinate
;   [0][5] = Virtual desktop Width
;   [0][6] = Virtual desktop Height
;
; For each monitor index i (1..$__g_aMonitorList[0][0]):
;   [i][0] = Monitor handle (HMONITOR)
;   [i][1] = Left coordinate of monitor
;   [i][2] = Top coordinate of monitor
;   [i][3] = Right coordinate of monitor
;   [i][4] = Bottom coordinate of monitor
;   [i][5] = IsPrimary (1 if primary, 0 otherwise) - FIXED: Now stores actual flag
;   [i][6] = Device name string (e.g. "\\.\DISPLAY1")
;
; ===============================================================================================================================
Global $__g_aMonitorList[1][7] = [[0, 0, 0, 0, 0, 0, ""]]
#EndRegion --- Global Variables ---

#Region --- OS Compatibility Functions ---
; #FUNCTION# ====================================================================================================================
; Name...........: __Monitor_IsWindowsVersionOrGreater
; Description....: Check if running on Windows version or greater (internal function)
; Syntax.........: __Monitor_IsWindowsVersionOrGreater($iMajor, $iMinor = 0)
; Parameters.....: $iMajor      - Major version number (e.g., 6 for Vista, 10 for Windows 10)
;                  $iMinor       - [optional] Minor version number. Default is 0
; Return values..: True if OS version is equal or greater, False otherwise
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Internal function for OS compatibility checking. Uses GetVersionEx API directly for accurate
;                  version detection, works on all AutoIt versions (even old ones where @OSVersion may be inaccurate).
;                  Windows XP = 5.1, Vista = 6.0, Win7 = 6.1, Win8 = 6.2, Win8.1 = 6.3, Win10 = 10.0
; ================================================================================================================================
Func __Monitor_IsWindowsVersionOrGreater($iMajor, $iMinor = 0)
    ; Use GetVersionEx API directly (works on all Windows versions and all AutoIt versions)
    ; This is more reliable than @OSVersion which may not be accurate on old AutoIt versions

    ; Define OSVERSIONINFOEX structure
    ; dwOSVersionInfoSize, dwMajorVersion, dwMinorVersion, dwBuildNumber, dwPlatformId,
    ; szCSDVersion[128], wServicePackMajor, wServicePackMinor, wSuiteMask, wProductType, wReserved
    Local $tOSVI = DllStructCreate("dword;dword;dword;dword;dword;wchar[128];ushort;ushort;ushort;byte;byte")
    If @error Then Return False

    ; Set structure size (first field)
    DllStructSetData($tOSVI, 1, DllStructGetSize($tOSVI))

    ; Call GetVersionExW (Unicode version, available from Windows 2000+)
    Local $aRet = DllCall("kernel32.dll", "bool", "GetVersionExW", "struct*", $tOSVI)
    If @error Or Not IsArray($aRet) Or Not $aRet[0] Then
        ; Fallback: Try ANSI version GetVersionExA (available from Windows 95+)
        ; Note: ANSI version uses char[128] instead of wchar[128]
        Local $tOSVIA = DllStructCreate("dword;dword;dword;dword;dword;char[128];ushort;ushort;ushort;byte;byte")
        If Not @error Then
            DllStructSetData($tOSVIA, 1, DllStructGetSize($tOSVIA))
            $aRet = DllCall("kernel32.dll", "bool", "GetVersionExA", "struct*", $tOSVIA)
            If @error Or Not IsArray($aRet) Or Not $aRet[0] Then
                ; Last resort: Use @OSVersion as fallback (but this may be inaccurate)
                Return __Monitor_FallbackOSVersionCheck($iMajor, $iMinor)
            EndIf
            ; Use ANSI version data
            Local $iOSMajor = DllStructGetData($tOSVIA, 2)
            Local $iOSMinor = DllStructGetData($tOSVIA, 3)

            ; Compare versions
            If $iOSMajor > $iMajor Then Return True
            If $iOSMajor = $iMajor And $iOSMinor >= $iMinor Then Return True
            Return False
        Else
            ; Last resort: Use @OSVersion as fallback
            Return __Monitor_FallbackOSVersionCheck($iMajor, $iMinor)
        EndIf
    EndIf

    ; Get version from structure (Unicode version)
    Local $iOSMajor = DllStructGetData($tOSVI, 2)
    Local $iOSMinor = DllStructGetData($tOSVI, 3)

    ; Handle Windows 10/11: GetVersionEx may return 6.3 for compatibility
    ; Check build number to distinguish Windows 10/11 from 8.1
    Local $iBuildNumber = DllStructGetData($tOSVI, 4)
    If $iOSMajor = 6 And $iOSMinor = 3 And $iBuildNumber >= 10000 Then
        ; Windows 10/11 (build number >= 10000)
        $iOSMajor = 10
        $iOSMinor = 0
    EndIf

    ; Compare versions
    If $iOSMajor > $iMajor Then Return True
    If $iOSMajor = $iMajor And $iOSMinor >= $iMinor Then Return True
    Return False
EndFunc   ;==>__Monitor_IsWindowsVersionOrGreater

; #FUNCTION# ====================================================================================================================
; Name...........: __Monitor_FallbackOSVersionCheck
; Description....: Fallback OS version check using @OSVersion macro (internal function)
; Syntax.........: __Monitor_FallbackOSVersionCheck($iMajor, $iMinor)
; Parameters.....: $iMajor      - Major version number
;                  $iMinor       - Minor version number
; Return values..: True if OS version is equal or greater, False otherwise
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Used only when GetVersionEx API fails. Less reliable than API call.
; ================================================================================================================================
Func __Monitor_FallbackOSVersionCheck($iMajor, $iMinor)
    ; Fallback to @OSVersion (may be inaccurate on old AutoIt versions, but better than nothing)
    Local $sOSVersion = @OSVersion
    Local $iOSMajor = 5, $iOSMinor = 1 ; Default to XP

    If StringInStr($sOSVersion, "WIN_11") Then
        $iOSMajor = 10
        $iOSMinor = 0
    ElseIf StringInStr($sOSVersion, "WIN_10") Then
        $iOSMajor = 10
        $iOSMinor = 0
    ElseIf StringInStr($sOSVersion, "WIN_8") Then
        ; Use @OSBuild to distinguish 8.0 vs 8.1 (8.1 has build >= 9600)
        If @OSBuild >= 9600 Then
            $iOSMajor = 6
            $iOSMinor = 3 ; Windows 8.1
        Else
            $iOSMajor = 6
            $iOSMinor = 2 ; Windows 8
        EndIf
    ElseIf StringInStr($sOSVersion, "WIN_7") Then
        $iOSMajor = 6
        $iOSMinor = 1
    ElseIf StringInStr($sOSVersion, "WIN_VISTA") Then
        $iOSMajor = 6
        $iOSMinor = 0
    ElseIf StringInStr($sOSVersion, "WIN_XP") Then
        $iOSMajor = 5
        $iOSMinor = 1
    ElseIf StringInStr($sOSVersion, "WIN_2003") Then
        $iOSMajor = 5
        $iOSMinor = 2
    EndIf

    ; Compare versions
    If $iOSMajor > $iMajor Then Return True
    If $iOSMajor = $iMajor And $iOSMinor >= $iMinor Then Return True
    Return False
EndFunc   ;==>__Monitor_FallbackOSVersionCheck

; #FUNCTION# ====================================================================================================================
; Name...........: __Monitor_IsWindows8_1OrGreater
; Description....: Check if running on Windows 8.1 or greater (internal function)
; Syntax.........: __Monitor_IsWindows8_1OrGreater()
; Parameters.....: None
; Return values..: True if Windows 8.1 or greater, False otherwise
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Internal function for checking if GetDpiForMonitor API is available
; ================================================================================================================================
Func __Monitor_IsWindows8_1OrGreater()
    ; Windows 8.1 = version 6.3, but GetVersionEx may return 6.3 for Windows 10/11 too
    ; We need to check build number: Windows 8.1 = build 9600-9999, Windows 10+ = build >= 10000

    ; Use GetVersionEx API directly
    Local $tOSVI = DllStructCreate("dword;dword;dword;dword;dword;wchar[128];ushort;ushort;ushort;byte;byte")
    If Not @error Then
        DllStructSetData($tOSVI, 1, DllStructGetSize($tOSVI))
        Local $aRet = DllCall("kernel32.dll", "bool", "GetVersionExW", "struct*", $tOSVI)
        If Not @error And IsArray($aRet) And $aRet[0] Then
            Local $iOSMajor = DllStructGetData($tOSVI, 2)
            Local $iOSMinor = DllStructGetData($tOSVI, 3)
            Local $iBuildNumber = DllStructGetData($tOSVI, 4)

            ; Windows 8.1 = 6.3 with build 9600-9999
            ; Windows 10+ = 6.3 (or 10.0) with build >= 10000
            If $iOSMajor = 6 And $iOSMinor = 3 Then
                ; Check build number
                If $iBuildNumber >= 10000 Then
                    Return True ; Windows 10 or later (>= 8.1)
                ElseIf $iBuildNumber >= 9600 Then
                    Return True ; Windows 8.1
                EndIf
            ElseIf $iOSMajor >= 10 Or ($iOSMajor = 6 And $iOSMinor >= 3) Then
                Return True ; Windows 8.1 or later
            EndIf
        EndIf
    EndIf

    ; Fallback to version check
    Return __Monitor_IsWindowsVersionOrGreater(6, 3)
EndFunc   ;==>__Monitor_IsWindows8_1OrGreater
#EndRegion --- OS Compatibility Functions ---


; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetFromPoint
; Description....: Get the monitor index from a screen coordinate or current mouse position
; Syntax.........: _Monitor_GetFromPoint([$iX = -1 [, $iY = -1]])
; Parameters.....: $iX        - [optional] X coordinate in virtual screen coordinates. Default is -1 (use mouse position)
;                  $iY        - [optional] Y coordinate in virtual screen coordinates. Default is -1 (use mouse position)
; Return values..: Success    - Monitor index (1..N)
;                  Failure    - 0, sets @error to non-zero:
;                  |@error = 1 - Invalid parameters or MouseGetPos failed
;                  |@error = 2 - Monitor not found at specified coordinates
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: If both $iX and $iY are -1 (default), function uses current mouse position.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetList, _Monitor_GetFromWindow, _Monitor_GetFromRect
; ================================================================================================================================
Func _Monitor_GetFromPoint($iX = -1, $iY = -1)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()

    ; Use WinAPI function if available
    If $iX = -1 Or $iY = -1 Then
        Local $aMouse = MouseGetPos()
        If @error Then Return SetError(1, 0, 0)
        $iX = $aMouse[0]
        $iY = $aMouse[1]
    EndIf

    Local $tPoint = DllStructCreate($tagPOINT)
    If @error Then Return SetError(1, 0, 0)
    DllStructSetData($tPoint, "X", $iX)
    DllStructSetData($tPoint, "Y", $iY)

    ; _WinAPI_MonitorFromPoint is available from Windows 2000+ (compatible with XP)
    Local $hMonitor = _WinAPI_MonitorFromPoint($tPoint, $MONITOR_DEFAULTTONEAREST)
    If @error Then
        ; Fallback to coordinate checking if WinAPI call fails
        $hMonitor = 0
    EndIf

    ; Find index in our list
    For $i = 1 To $__g_aMonitorList[0][0]
        If $__g_aMonitorList[$i][0] = $hMonitor Then Return $i
    Next

    ; Fallback to coordinate checking
    For $i = 1 To $__g_aMonitorList[0][0]
        If $iX >= $__g_aMonitorList[$i][1] _
                And $iX < $__g_aMonitorList[$i][3] _
                And $iY >= $__g_aMonitorList[$i][2] _
                And $iY < $__g_aMonitorList[$i][4] Then
            Return $i
        EndIf
    Next
    Return SetError(2, 0, 0)
EndFunc   ;==>_Monitor_GetFromPoint

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetFromWindow
; Description....: Get the monitor index that contains the specified window
; Syntax.........: _Monitor_GetFromWindow($hWnd [, $iFlag = $MONITOR_DEFAULTTONEAREST])
; Parameters.....: $hWnd      - Window handle or title string. Can be HWND or window title
;                  $iFlag     - [optional] Monitor flag. Default is $MONITOR_DEFAULTTONEAREST
;                              Can be: $MONITOR_DEFAULTTONULL, $MONITOR_DEFAULTTOPRIMARY, $MONITOR_DEFAULTTONEAREST
; Return values..: Success    - Monitor index (1..N)
;                  Failure    - 0, sets @error to non-zero:
;                  |@error = 1 - Invalid window handle or window not found
;                  |@error = 2 - WinAPI MonitorFromWindow call failed
;                  |@error = 3 - Monitor handle not found in internal list
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Function accepts both window handles and window titles. Automatically converts title to handle.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetList, _Monitor_GetFromPoint, _Monitor_GetFromRect
; ================================================================================================================================
Func _Monitor_GetFromWindow($hWnd, $iFlag = $MONITOR_DEFAULTTONEAREST)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If Not IsHWnd($hWnd) Then $hWnd = WinGetHandle($hWnd)
    If Not $hWnd Then Return SetError(1, 0, 0)

    ; _WinAPI_MonitorFromWindow is available from Windows 2000+ (compatible with XP)
    Local $hMonitor = _WinAPI_MonitorFromWindow($hWnd, $iFlag)
    If @error Or Not $hMonitor Then Return SetError(2, 0, 0)

    For $i = 1 To $__g_aMonitorList[0][0]
        If $__g_aMonitorList[$i][0] = $hMonitor Then Return $i
    Next
    Return SetError(3, 0, 0)
EndFunc   ;==>_Monitor_GetFromWindow

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetFromRect
; Description....: Get the monitor index that has the largest intersection with the specified rectangle
; Syntax.........: _Monitor_GetFromRect($iLeft, $iTop, $iRight, $iBottom [, $iFlag = $MONITOR_DEFAULTTONEAREST])
; Parameters.....: $iLeft     - Left coordinate of the rectangle in virtual screen coordinates
;                  $iTop      - Top coordinate of the rectangle in virtual screen coordinates
;                  $iRight    - Right coordinate of the rectangle in virtual screen coordinates
;                  $iBottom   - Bottom coordinate of the rectangle in virtual screen coordinates
;                  $iFlag     - [optional] Monitor flag. Default is $MONITOR_DEFAULTTONEAREST
;                              Can be: $MONITOR_DEFAULTTONULL, $MONITOR_DEFAULTTOPRIMARY, $MONITOR_DEFAULTTONEAREST
; Return values..: Success    - Monitor index (1..N)
;                  Failure    - 0, sets @error to non-zero:
;                  |@error = 1 - DllStructCreate failed or WinAPI MonitorFromRect call failed
;                  |@error = 2 - Monitor not found in internal list
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Coordinates should be in virtual screen coordinate system (can span multiple monitors).
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetList, _Monitor_GetFromPoint, _Monitor_GetFromWindow
; ================================================================================================================================
Func _Monitor_GetFromRect($iLeft, $iTop, $iRight, $iBottom, $iFlag = $MONITOR_DEFAULTTONEAREST)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()

    Local $tRect = DllStructCreate($tagRECT)
    If @error Then Return SetError(1, 0, 0)
    DllStructSetData($tRect, "Left", $iLeft)
    DllStructSetData($tRect, "Top", $iTop)
    DllStructSetData($tRect, "Right", $iRight)
    DllStructSetData($tRect, "Bottom", $iBottom)

    ; _WinAPI_MonitorFromRect is available from Windows 2000+ (compatible with XP)
    Local $hMonitor = _WinAPI_MonitorFromRect($tRect, $iFlag)
    If @error Or Not $hMonitor Then Return SetError(1, 0, 0)

    For $i = 1 To $__g_aMonitorList[0][0]
        If $__g_aMonitorList[$i][0] = $hMonitor Then Return $i
    Next
    Return SetError(2, 0, 0)
EndFunc   ;==>_Monitor_GetFromRect

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetWorkArea
; Description....: Get working area of a specific monitor (excluding taskbar and system bars)
; Syntax.........: _Monitor_GetWorkArea($iMonitor, ByRef $left, ByRef $top, ByRef $right, ByRef $bottom)
; Parameters.....: $iMonitor   - Monitor index (1..N)
;                  $left       - [out] Left coordinate of work area (virtual screen coordinates)
;                  $top        - [out] Top coordinate of work area (virtual screen coordinates)
;                  $right      - [out] Right coordinate of work area (virtual screen coordinates)
;                  $bottom     - [out] Bottom coordinate of work area (virtual screen coordinates)
; Return values..: Success     - 1
;                  Failure     - 0, sets @error to non-zero:
;                  |@error = 1 - Invalid monitor index
;                  |@error = 2 - WinAPI GetMonitorInfo call failed
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Work area excludes taskbar and other system bars. Use _Monitor_GetBounds() for full monitor area.
;                  All coordinates are in virtual screen coordinate system.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetBounds, _Monitor_GetInfo, _Monitor_GetList
; ================================================================================================================================
Func _Monitor_GetWorkArea($iMonitor, ByRef $left, ByRef $top, ByRef $right, ByRef $bottom)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, 0)

    Local $hMonitor = $__g_aMonitorList[$iMonitor][0]
    ; _WinAPI_GetMonitorInfo is available from Windows 2000+ (compatible with XP)
    Local $aInfo = _WinAPI_GetMonitorInfo($hMonitor)
    If @error Or Not IsArray($aInfo) Then Return SetError(2, 0, 0)

    Local $tWorkArea = $aInfo[1]
    If Not IsDllStruct($tWorkArea) Then Return SetError(2, 0, 0)
    $left = DllStructGetData($tWorkArea, "Left")
    $top = DllStructGetData($tWorkArea, "Top")
    $right = DllStructGetData($tWorkArea, "Right")
    $bottom = DllStructGetData($tWorkArea, "Bottom")
    Return 1
EndFunc   ;==>_Monitor_GetWorkArea

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetBounds
; Description....: Get full bounds of a specific monitor (including taskbar and all system areas)
; Syntax.........: _Monitor_GetBounds($iMonitor, ByRef $left, ByRef $top, ByRef $right, ByRef $bottom)
; Parameters.....: $iMonitor   - Monitor index (1..N)
;                  $left       - [out] Left coordinate of monitor (virtual screen coordinates)
;                  $top        - [out] Top coordinate of monitor (virtual screen coordinates)
;                  $right      - [out] Right coordinate of monitor (virtual screen coordinates)
;                  $bottom     - [out] Bottom coordinate of monitor (virtual screen coordinates)
; Return values..: Success     - 1
;                  Failure     - 0, sets @error = 1 (Invalid monitor index)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Returns the full physical bounds of the monitor including all system bars (taskbar, etc.).
;                  All coordinates are in virtual screen coordinate system.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
;                  For usable area excluding taskbar, use _Monitor_GetWorkArea() instead.
; Related........: _Monitor_GetWorkArea, _Monitor_GetInfo, _Monitor_GetList
; ================================================================================================================================
Func _Monitor_GetBounds($iMonitor, ByRef $left, ByRef $top, ByRef $right, ByRef $bottom)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, 0)

    $left = $__g_aMonitorList[$iMonitor][1]
    $top = $__g_aMonitorList[$iMonitor][2]
    $right = $__g_aMonitorList[$iMonitor][3]
    $bottom = $__g_aMonitorList[$iMonitor][4]
    Return 1
EndFunc   ;==>_Monitor_GetBounds

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetInfo
; Description....: Get detailed information about a monitor
; Syntax.........: _Monitor_GetInfo($iMonitor)
; Parameters.....: $iMonitor   - Monitor index (1..N)
; Return values..: Success     - Array with 11 elements:
;                  |[0]  - Monitor handle (HMONITOR)
;                  |[1]  - Left coordinate of monitor bounds (virtual screen coordinates)
;                  |[2]  - Top coordinate of monitor bounds (virtual screen coordinates)
;                  |[3]  - Right coordinate of monitor bounds (virtual screen coordinates)
;                  |[4]  - Bottom coordinate of monitor bounds (virtual screen coordinates)
;                  |[5]  - Left coordinate of work area (virtual screen coordinates)
;                  |[6]  - Top coordinate of work area (virtual screen coordinates)
;                  |[7]  - Right coordinate of work area (virtual screen coordinates)
;                  |[8]  - Bottom coordinate of work area (virtual screen coordinates)
;                  |[9]  - IsPrimary flag (1 = Primary, 0 = Secondary)
;                  |[10] - Device name string (e.g., "\\.\DISPLAY1")
;                  Failure     - 0, sets @error to non-zero:
;                  |@error = 1 - Invalid monitor index
;                  |@error = 2 - WinAPI GetMonitorInfo call failed
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: This is the most comprehensive function to get all monitor information at once.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetList, _Monitor_GetBounds, _Monitor_GetWorkArea, _Monitor_GetPrimary
; ================================================================================================================================
Func _Monitor_GetInfo($iMonitor)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, 0)

    Local $hMonitor = $__g_aMonitorList[$iMonitor][0]
    ; _WinAPI_GetMonitorInfo is available from Windows 2000+ (compatible with XP)
    Local $aInfo = _WinAPI_GetMonitorInfo($hMonitor)
    If @error Or Not IsArray($aInfo) Then Return SetError(2, 0, 0)

    Local $tMonitorRect = $aInfo[0]
    Local $tWorkRect = $aInfo[1]
    If Not IsDllStruct($tMonitorRect) Or Not IsDllStruct($tWorkRect) Then Return SetError(2, 0, 0)

    Local $aResult[11]
    $aResult[0] = $hMonitor
    $aResult[1] = DllStructGetData($tMonitorRect, "Left")
    $aResult[2] = DllStructGetData($tMonitorRect, "Top")
    $aResult[3] = DllStructGetData($tMonitorRect, "Right")
    $aResult[4] = DllStructGetData($tMonitorRect, "Bottom")
    $aResult[5] = DllStructGetData($tWorkRect, "Left")
    $aResult[6] = DllStructGetData($tWorkRect, "Top")
    $aResult[7] = DllStructGetData($tWorkRect, "Right")
    $aResult[8] = DllStructGetData($tWorkRect, "Bottom")
    $aResult[9] = ($aInfo[2] <> 0) ; IsPrimary
    $aResult[10] = $aInfo[3] ; DeviceName

    Return $aResult
EndFunc   ;==>_Monitor_GetInfo

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetDisplaySettings
; Description....: Get display settings for a monitor (resolution, color depth, refresh rate, etc.)
; Syntax.........: _Monitor_GetDisplaySettings($iMonitor [, $iMode = $ENUM_CURRENT_SETTINGS])
; Parameters.....: $iMonitor   - Monitor index (1..N)
;                  $iMode      - [optional] Display mode index. Default is $ENUM_CURRENT_SETTINGS
;                              Use $ENUM_CURRENT_SETTINGS to get current active settings
;                              Use index number (0..N) to enumerate available modes
; Return values..: Success     - Array with 5 elements:
;                  |[0] - Width (pixels)
;                  |[1] - Height (pixels)
;                  |[2] - Bits per pixel (color depth)
;                  |[3] - Refresh rate (Hz)
;                  |[4] - Display mode flags
;                  Failure     - 0, sets @error to non-zero:
;                  |@error = 1 - Invalid monitor index
;                  |@error = 2 - GetInfo failed (could not get device name)
;                  |@error = 3 - WinAPI EnumDisplaySettings call failed
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Use $ENUM_CURRENT_SETTINGS to get the currently active display mode.
;                  Use _Monitor_EnumAllDisplayModes() to get all available display modes.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetResolution, _Monitor_EnumAllDisplayModes, _Monitor_GetList
; ================================================================================================================================
Func _Monitor_GetDisplaySettings($iMonitor, $iMode = $ENUM_CURRENT_SETTINGS)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, 0)

    Local $sDevice = $__g_aMonitorList[$iMonitor][6]
    If $sDevice = "" Then
        Local $aInfo = _Monitor_GetInfo($iMonitor)
        If @error Then Return SetError(2, 0, 0)
        $sDevice = $aInfo[10]
    EndIf

    ; _WinAPI_EnumDisplaySettings is available from Windows 95+ (fully compatible with XP)
    Local $aSettings = _WinAPI_EnumDisplaySettings($sDevice, $iMode)
    If @error Or Not IsArray($aSettings) Then Return SetError(3, 0, 0)

    Return $aSettings
EndFunc   ;==>_Monitor_GetDisplaySettings

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetResolution
; Description....: Get the current resolution (width and height) of a monitor
; Syntax.........: _Monitor_GetResolution($iMonitor)
; Parameters.....: $iMonitor   - Monitor index (1..N)
; Return values..: Success     - Array with 2 elements:
;                  |[0] - Width in pixels
;                  |[1] - Height in pixels
;                  Failure     - 0, sets @error to non-zero:
;                  |@error = 1 - Invalid monitor index
;                  |@error = 2 - GetDisplaySettings failed
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: This is a convenience function that returns only width and height from display settings.
;                  For full display settings (color depth, refresh rate, etc.), use _Monitor_GetDisplaySettings().
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetDisplaySettings, _Monitor_GetInfo, _Monitor_GetList
; ================================================================================================================================
Func _Monitor_GetResolution($iMonitor)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, 0)

    Local $aSettings = _Monitor_GetDisplaySettings($iMonitor)
    If @error Then Return SetError(2, @error, 0)

    Local $aResult[2] = [$aSettings[0], $aSettings[1]]
    Return $aResult
EndFunc   ;==>_Monitor_GetResolution

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetPrimary
; Description....: Get the index of the primary monitor
; Syntax.........: _Monitor_GetPrimary()
; Parameters.....: None
; Return values..: Success     - Monitor index (1..N) of the primary monitor
;                  Failure     - 0, sets @error = 1 (No primary monitor found)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Primary monitor is the monitor that contains the taskbar by default in Windows.
;                  Function first checks cached IsPrimary flags, then falls back to querying system if needed.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetList, _Monitor_GetInfo, _Monitor_GetCount
; ================================================================================================================================
Func _Monitor_GetPrimary()
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()

    ; Use cached IsPrimary flag if available
    For $i = 1 To $__g_aMonitorList[0][0]
        If $__g_aMonitorList[$i][5] = 1 Then Return $i
    Next

    ; Fallback: query from system
    For $i = 1 To $__g_aMonitorList[0][0]
        Local $aInfo = _Monitor_GetInfo($i)
        If Not @error And $aInfo[9] = 1 Then Return $i
    Next
    Return SetError(1, 0, 0)
EndFunc   ;==>_Monitor_GetPrimary

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetCount
; Description....: Returns the total number of connected monitors
; Syntax.........: _Monitor_GetCount()
; Parameters.....: None
; Return values..: Success     - Number of monitors (>= 1)
;                  Failure     - 0, sets @error = 1 (Enumeration failed)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Function first tries GetSystemMetrics API for fastest response.
;                  Falls back to cached monitor list if API call fails.
;                  Automatically validates and refreshes monitor list if count mismatch detected.
; Related........: _Monitor_GetList, _Monitor_Refresh, _Monitor_GetPrimary
; ================================================================================================================================
Func _Monitor_GetCount()
    ; Try GetSystemMetrics first (compatible with Windows XP and later)
    ; Note: SM_CMONITORS is available from Windows 2000+
    Local $aRet = DllCall("user32.dll", "int", "GetSystemMetrics", "int", $SM_CMONITORS)
    If @error Or Not IsArray($aRet) Or $aRet[0] < 1 Then
        ; Fallback to our cached list
        If $__g_aMonitorList[0][0] = 0 Then
            If _Monitor_GetList() = -1 Then Return SetError(1, 0, 0)
        EndIf
        Return $__g_aMonitorList[0][0]
    Else
        ; Validate count matches our list (refresh if needed)
        If $__g_aMonitorList[0][0] <> $aRet[0] Then
            _Monitor_GetList()
        EndIf
        Return $aRet[0]
    EndIf
EndFunc   ;==>_Monitor_GetCount

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetVirtualBounds
; Description....: Get bounding rectangle of all monitors combined (the "virtual screen")
; Syntax.........: _Monitor_GetVirtualBounds()
; Parameters.....: None
; Return values..: Success     - Array with 4 elements:
;                  |[0] - Left coordinate of virtual screen
;                  |[1] - Top coordinate of virtual screen
;                  |[2] - Width of virtual screen in pixels
;                  |[3] - Height of virtual screen in pixels
;                  Failure     - 0, sets @error = 1 (DllCall failed)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Virtual screen is the bounding rectangle that encompasses all connected monitors.
;                  Function uses GetSystemMetrics API. Falls back to cached virtual bounds if API fails.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetList, _Monitor_GetBounds, _Monitor_GetCount
; ================================================================================================================================
Func _Monitor_GetVirtualBounds()
    ; GetSystemMetrics for virtual screen is available from Windows 2000+
    ; All SM_*VIRTUALSCREEN constants are supported on Windows XP and later
    Local $aL = DllCall("user32.dll", "int", "GetSystemMetrics", "int", $SM_XVIRTUALSCREEN)
    Local $aT = DllCall("user32.dll", "int", "GetSystemMetrics", "int", $SM_YVIRTUALSCREEN)
    Local $aW = DllCall("user32.dll", "int", "GetSystemMetrics", "int", $SM_CXVIRTUALSCREEN)
    Local $aH = DllCall("user32.dll", "int", "GetSystemMetrics", "int", $SM_CYVIRTUALSCREEN)

    ; Validate all calls succeeded (comprehensive error checking for XP compatibility)
    Local $bError = False
    If @error Then $bError = True
    If Not IsArray($aL) Or Not IsArray($aT) Or Not IsArray($aW) Or Not IsArray($aH) Then $bError = True
    If $bError Then
        ; Fallback to cached virtual bounds
        If $__g_aMonitorList[0][0] = 0 Then
            If _Monitor_GetList() = -1 Then Return SetError(1, 0, 0)
        EndIf
        Local $aRet[4] = [$__g_aMonitorList[0][1], $__g_aMonitorList[0][2], $__g_aMonitorList[0][5], $__g_aMonitorList[0][6]]
        Return $aRet
    EndIf

    ; Validate returned values are reasonable
    If $aL[0] < -32768 Or $aT[0] < -32768 Or $aW[0] < 1 Or $aH[0] < 1 Then
        ; Invalid values, use fallback
        If $__g_aMonitorList[0][0] = 0 Then
            If _Monitor_GetList() = -1 Then Return SetError(1, 0, 0)
        EndIf
        Local $aRet[4] = [$__g_aMonitorList[0][1], $__g_aMonitorList[0][2], $__g_aMonitorList[0][5], $__g_aMonitorList[0][6]]
        Return $aRet
    EndIf

    Local $a[4] = [$aL[0], $aT[0], $aW[0], $aH[0]]
    Return $a
EndFunc   ;==>_Monitor_GetVirtualBounds

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_ToVirtual
; Description....: Convert local monitor coordinates to virtual screen coordinates
; Syntax.........: _Monitor_ToVirtual($iMonitor, $x, $y)
; Parameters.....: $iMonitor   - Monitor index (1..N)
;                  $x          - X coordinate in local monitor coordinates (0-based from monitor's left edge)
;                  $y          - Y coordinate in local monitor coordinates (0-based from monitor's top edge)
; Return values..: Success     - Array with 2 elements [X, Y] in virtual screen coordinates
;                  Failure     - 0, sets @error = 1 (Invalid monitor index)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Local coordinates are relative to the monitor (0,0 is top-left of that monitor).
;                  Virtual coordinates are absolute in the virtual screen coordinate system.
;                  Coordinate validation is optional (currently commented out for flexibility).
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_FromVirtual, _Monitor_GetBounds, _Monitor_GetList
; ================================================================================================================================
Func _Monitor_ToVirtual($iMonitor, $x, $y)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, 0)

    ; Optional: Validate coordinates are within monitor bounds
    Local $iWidth = $__g_aMonitorList[$iMonitor][3] - $__g_aMonitorList[$iMonitor][1]
    Local $iHeight = $__g_aMonitorList[$iMonitor][4] - $__g_aMonitorList[$iMonitor][2]
    If $x < 0 Or $y < 0 Or $x > $iWidth Or $y > $iHeight Then
        ; Return SetError(2, 0, 0)  ; Uncomment if strict validation needed
    EndIf

    Local $aRet[2] = [$__g_aMonitorList[$iMonitor][1] + $x, $__g_aMonitorList[$iMonitor][2] + $y]
    Return $aRet
EndFunc   ;==>_Monitor_ToVirtual

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_FromVirtual
; Description....: Convert virtual screen coordinates back to local monitor coordinates
; Syntax.........: _Monitor_FromVirtual($iMonitor, $x, $y)
; Parameters.....: $iMonitor   - Monitor index (1..N)
;                  $x          - X coordinate in virtual screen coordinates
;                  $y          - Y coordinate in virtual screen coordinates
; Return values..: Success     - Array with 2 elements [X, Y] in local monitor coordinates (0-based)
;                  Failure     - 0, sets @error to non-zero:
;                  |@error = 1 - Invalid monitor index
;                  |@error = 2 - Coordinates are not within the specified monitor's bounds
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Local coordinates are relative to the monitor (0,0 is top-left of that monitor).
;                  Virtual coordinates are absolute in the virtual screen coordinate system.
;                  Function validates that coordinates are actually on the specified monitor.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_ToVirtual, _Monitor_GetBounds, _Monitor_GetList
; ================================================================================================================================
Func _Monitor_FromVirtual($iMonitor, $x, $y)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, 0)

    ; Validate coordinates are on this monitor
    If $x < $__g_aMonitorList[$iMonitor][1] Or $x >= $__g_aMonitorList[$iMonitor][3] _
            Or $y < $__g_aMonitorList[$iMonitor][2] Or $y >= $__g_aMonitorList[$iMonitor][4] Then
        Return SetError(2, 0, 0)
    EndIf

    Local $aRet[2] = [$x - $__g_aMonitorList[$iMonitor][1], $y - $__g_aMonitorList[$iMonitor][2]]
    Return $aRet
EndFunc   ;==>_Monitor_FromVirtual

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_IsVisibleWindow
; Description....: Check if a window is visible and is a top-level window (not a child window or tool window)
; Syntax.........: _Monitor_IsVisibleWindow($hWnd)
; Parameters.....: $hWnd       - Window handle or title string. Can be HWND or window title
; Return values..: Success     - True if window is visible and top-level, False otherwise
;                  Failure     - False, sets @error = 1 (Invalid window handle)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Function checks for: WS_VISIBLE flag, not WS_CHILD, and not WS_EX_TOOLWINDOW.
;                  Accepts both window handles and window titles. Automatically converts title to handle.
;                  This is useful for filtering which windows should be moved between monitors.
; Related........: _Monitor_MoveWindowToScreen, _Monitor_GetFromWindow
; ================================================================================================================================
Func _Monitor_IsVisibleWindow($hWnd)
    If Not IsHWnd($hWnd) Then $hWnd = WinGetHandle($hWnd)
    If Not $hWnd Or Not WinExists($hWnd) Then Return SetError(1, 0, False)
    ; _WinAPI_GetWindowLong is available from Windows 95+ (fully compatible with XP)
    Local $style = _WinAPI_GetWindowLong($hWnd, $GWL_STYLE)
    If @error Then Return SetError(1, 0, False)
    If BitAND($style, $WS_VISIBLE) = 0 Then Return False
    If BitAND($style, $WS_CHILD) <> 0 Then Return False
    Local $ex = _WinAPI_GetWindowLong($hWnd, $GWL_EXSTYLE)
    If @error Then Return False
    If BitAND($ex, $WS_EX_TOOLWINDOW) <> 0 Then Return False
    Return True
EndFunc   ;==>_Monitor_IsVisibleWindow

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_MoveWindowToScreen
; Description....: Move a visible window to a specific monitor (centered if coordinates not specified)
; Syntax.........: _Monitor_MoveWindowToScreen($vTitle [, $sText = "" [, $iMonitor = -1 [, $x = -1 [, $y = -1 [, $bUseWorkArea = True]]]]])
; Parameters.....: $vTitle     - Window title or handle. Can be HWND, title string, or class string
;                  $sText      - [optional] Window text (for matching with title). Default is ""
;                  $iMonitor   - [optional] Target monitor index (1..N). Default is -1 (uses monitor 1)
;                  $x          - [optional] X position on monitor. Default is -1 (centers horizontally)
;                  $y          - [optional] Y position on monitor. Default is -1 (centers vertically)
;                  $bUseWorkArea - [optional] Use work area instead of full bounds. Default is True
; Return values..: Success     - 1
;                  Failure     - 0, sets @error to non-zero:
;                  |@error = 1 - Window not visible or not top-level
;                  |@error = 2 - Invalid monitor index or GetWorkArea/GetBounds failed
;                  |@error = 3 - WinGetPos failed (could not get window position/size)
;                  |@error = 4 - Window too large to fit on monitor
;                  |@error = 5 - WinMove failed
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: If both $x and $y are -1, window is centered on the monitor.
;                  If $bUseWorkArea is True, positioning is relative to work area (excludes taskbar).
;                  Function ensures window stays within monitor bounds (adjusts if necessary).
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_MoveWindowToAll, _Monitor_IsVisibleWindow, _Monitor_GetWorkArea, _Monitor_GetBounds
; ================================================================================================================================
Func _Monitor_MoveWindowToScreen($vTitle, $sText = "", $iMonitor = -1, $x = -1, $y = -1, $bUseWorkArea = True)
    Local $hWnd = IsHWnd($vTitle) ? $vTitle : WinGetHandle($vTitle, $sText)
    If Not _Monitor_IsVisibleWindow($hWnd) Then Return SetError(1, 0, 0)

    If $iMonitor = -1 Then $iMonitor = 1
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(2, 0, 0)

    Local $aWinPos = WinGetPos($hWnd)
    If @error Or Not IsArray($aWinPos) Then Return SetError(3, 0, 0)

    Local $iLeft, $iTop, $iRight, $iBottom
    If $bUseWorkArea Then
        If Not _Monitor_GetWorkArea($iMonitor, $iLeft, $iTop, $iRight, $iBottom) Then Return SetError(2, @error, 0)
    Else
        If Not _Monitor_GetBounds($iMonitor, $iLeft, $iTop, $iRight, $iBottom) Then Return SetError(2, @error, 0)
    EndIf

    Local $iWidth = $iRight - $iLeft
    Local $iHeight = $iBottom - $iTop

    ; Check if window fits on monitor
    If $aWinPos[2] > $iWidth Or $aWinPos[3] > $iHeight Then
        Return SetError(4, 0, 0)
    EndIf

    If $x = -1 Or $y = -1 Then
        $x = $iLeft + ($iWidth - $aWinPos[2]) / 2
        $y = $iTop + ($iHeight - $aWinPos[3]) / 2
    Else
        $x += $iLeft
        $y += $iTop
        ; Ensure window stays within bounds
        If $x + $aWinPos[2] > $iRight Then $x = $iRight - $aWinPos[2]
        If $y + $aWinPos[3] > $iBottom Then $y = $iBottom - $aWinPos[3]
        If $x < $iLeft Then $x = $iLeft
        If $y < $iTop Then $y = $iTop
    EndIf

    WinMove($hWnd, "", $x, $y)
    If @error Then Return SetError(5, 0, 0)
    Return 1
EndFunc   ;==>_Monitor_MoveWindowToScreen

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_MoveWindowToAll
; Description....: Move a visible window sequentially across all monitors with a delay between moves
; Syntax.........: _Monitor_MoveWindowToAll($vTitle [, $sText = "" [, $bCenter = True [, $iDelay = 1000]]])
; Parameters.....: $vTitle     - Window title or handle. Can be HWND, title string, or class string
;                  $sText      - [optional] Window text (for matching with title). Default is ""
;                  $bCenter    - [optional] Center window on each monitor. Default is True
;                              If False, window is positioned at (50, 50) on each monitor
;                  $iDelay     - [optional] Delay in milliseconds between moves. Default is 1000
; Return values..: Success     - 1
;                  Failure     - 0, sets @error = 1 (Window not visible or not top-level)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: This is a demonstration function that moves a window to each monitor in sequence.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
;                  Useful for testing multi-monitor setups or demonstrating window movement.
; Related........: _Monitor_MoveWindowToScreen, _Monitor_IsVisibleWindow, _Monitor_GetCount
; ================================================================================================================================
Func _Monitor_MoveWindowToAll($vTitle, $sText = "", $bCenter = True, $iDelay = 1000)
    Local $hWnd = IsHWnd($vTitle) ? $vTitle : WinGetHandle($vTitle, $sText)
    If Not _Monitor_IsVisibleWindow($hWnd) Then Return SetError(1, 0, 0)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()

    For $i = 1 To $__g_aMonitorList[0][0]
        If $bCenter Then
            _Monitor_MoveWindowToScreen($hWnd, "", $i)
        Else
            _Monitor_MoveWindowToScreen($hWnd, "", $i, 50, 50)
        EndIf
        Sleep($iDelay)
    Next
    Return 1
EndFunc   ;==>_Monitor_MoveWindowToAll

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_EnumAllDisplayModes
; Description....: Enumerate all available display modes for a monitor
; Syntax.........: _Monitor_EnumAllDisplayModes($iMonitor)
; Parameters.....: $iMonitor   - Monitor index (1..N)
; Return values..: Success     - 2D array with display modes:
;                  |[0][0] - Number of modes found
;                  |[n][0] - Width in pixels for mode n
;                  |[n][1] - Height in pixels for mode n
;                  |[n][2] - Bits per pixel (color depth) for mode n
;                  |[n][3] - Refresh rate in Hz for mode n
;                  |[n][4] - Display mode flags for mode n
;                  Failure     - 0, sets @error to non-zero:
;                  |@error = 1 - Invalid monitor index
;                  |@error = 2 - GetInfo failed (could not get device name)
;                  |@error = 3 - No display modes found
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Returns all supported display modes for the specified monitor.
;                  Use _Monitor_GetDisplaySettings() to get only the current active mode.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetDisplaySettings, _Monitor_GetResolution, _Monitor_GetList
; ================================================================================================================================
Func _Monitor_EnumAllDisplayModes($iMonitor)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, 0)

    Local $aInfo = _Monitor_GetInfo($iMonitor)
    If @error Then Return SetError(2, 0, 0)
    Local $sDevice = $aInfo[10]

    Local $aModes[1][5]
    $aModes[0][0] = 0
    Local $iIndex = 0

    While True
        Local $aMode = _WinAPI_EnumDisplaySettings($sDevice, $iIndex)
        If @error Then ExitLoop

        ReDim $aModes[$aModes[0][0] + 2][5]
        $aModes[0][0] += 1
        $aModes[$aModes[0][0]][0] = $aMode[0]
        $aModes[$aModes[0][0]][1] = $aMode[1]
        $aModes[$aModes[0][0]][2] = $aMode[2]
        $aModes[$aModes[0][0]][3] = $aMode[3]
        $aModes[$aModes[0][0]][4] = $aMode[4]

        $iIndex += 1
    WEnd

    If $aModes[0][0] = 0 Then Return SetError(3, 0, 0)
    Return $aModes
EndFunc   ;==>_Monitor_EnumAllDisplayModes

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetList
; Description....: Enumerate all connected monitors and fill the global monitor list with their information
; Syntax.........: _Monitor_GetList()
; Parameters.....: None
; Return values..: Success     - Number of monitors detected (>= 1)
;                  Failure     - -1, sets @error = 1 (WinAPI EnumDisplayMonitors call failed)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: This is the core function that initializes the monitor list. Most other functions call this
;                  automatically if the list is not initialized (when $__g_aMonitorList[0][0] = 0).
;                  Populates the global array $__g_aMonitorList with monitor handles, coordinates, and device names.
;                  Also stores virtual desktop bounds and IsPrimary flags for each monitor.
; Related........: _Monitor_Refresh, _Monitor_GetCount, _Monitor_GetInfo
; ================================================================================================================================
Func _Monitor_GetList()
    ; _WinAPI_EnumDisplayMonitors is available from Windows 2000+ (compatible with XP)
    Local $aMonitors = _WinAPI_EnumDisplayMonitors()
    If @error Or Not IsArray($aMonitors) Or $aMonitors[0][0] = 0 Then
        Return SetError(1, 0, -1)
    EndIf

    ReDim $__g_aMonitorList[$aMonitors[0][0] + 1][7]
    $__g_aMonitorList[0][0] = $aMonitors[0][0]

    Local $l_aVirtual = _Monitor_GetVirtualBounds()
    Local $l_vRight = $l_aVirtual[0] + $l_aVirtual[2]
    Local $l_vBottom = $l_aVirtual[1] + $l_aVirtual[3]
    $__g_aMonitorList[0][1] = $l_aVirtual[0]
    $__g_aMonitorList[0][2] = $l_aVirtual[1]
    $__g_aMonitorList[0][3] = $l_vRight
    $__g_aMonitorList[0][4] = $l_vBottom
    $__g_aMonitorList[0][5] = $l_aVirtual[2]
    $__g_aMonitorList[0][6] = $l_aVirtual[3]

    For $i = 1 To $aMonitors[0][0]
        Local $hMonitor = $aMonitors[$i][0]
        Local $tRect = $aMonitors[$i][1]

        $__g_aMonitorList[$i][0] = $hMonitor
        $__g_aMonitorList[$i][1] = DllStructGetData($tRect, "Left")
        $__g_aMonitorList[$i][2] = DllStructGetData($tRect, "Top")
        $__g_aMonitorList[$i][3] = DllStructGetData($tRect, "Right")
        $__g_aMonitorList[$i][4] = DllStructGetData($tRect, "Bottom")

        ; Get additional info - FIXED: Store IsPrimary flag correctly
        ; _WinAPI_GetMonitorInfo is available from Windows 2000+ (compatible with XP)
        Local $aInfo = _WinAPI_GetMonitorInfo($hMonitor)
        If Not @error And IsArray($aInfo) Then
            ; FIXED: Store IsPrimary flag (0 or 1) instead of pointer
            $__g_aMonitorList[$i][5] = ($aInfo[2] <> 0) ? 1 : 0  ; IsPrimary flag
            $__g_aMonitorList[$i][6] = $aInfo[3] ; Device name
        Else
            ; Safe fallback if GetMonitorInfo fails (shouldn't happen on XP, but safe anyway)
            $__g_aMonitorList[$i][5] = 0
            $__g_aMonitorList[$i][6] = ""
        EndIf
    Next

    Return $__g_aMonitorList[0][0]
EndFunc   ;==>_Monitor_GetList

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_ShowInfo
; Description....: Display monitor coordinates and detailed information in a message box and console
; Syntax.........: _Monitor_ShowInfo([$bShowMsgBox = 1 [, $iTimeout = 10]])
; Parameters.....: $bShowMsgBox - [optional] Show message box. Default is 1 (True)
;                  $iTimeout    - [optional] Message box timeout in seconds. Default is 10
; Return values..: Success      - String containing formatted monitor information
;                  Failure      - Empty string "", sets @error = 1 (Enumeration failed)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Displays comprehensive information about all monitors including bounds, work areas,
;                  resolutions, refresh rates, and device names. Information is written to console and
;                  optionally shown in a message box.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
;                  Useful for debugging and displaying system monitor configuration.
; Related........: _Monitor_GetList, _Monitor_GetInfo, _Monitor_GetDisplaySettings
; ================================================================================================================================
Func _Monitor_ShowInfo($bShowMsgBox = 1, $iTimeout = 10)
    If $__g_aMonitorList[0][0] = 0 Then
        If _Monitor_GetList() = -1 Then Return SetError(1, 0, "")
    EndIf
    Local $sMsg = "> Total Monitors: " & $__g_aMonitorList[0][0] & @CRLF & @CRLF
    $sMsg &= StringFormat("+ Virtual Desktop: " & @CRLF & "Left=%d, Top=%d, Right=%d, Bottom=%d, Width=%d, Height=%d", $__g_aMonitorList[0][1], $__g_aMonitorList[0][2], $__g_aMonitorList[0][3], $__g_aMonitorList[0][4], $__g_aMonitorList[0][5], $__g_aMonitorList[0][6]) & @CRLF & @CRLF

    For $i = 1 To $__g_aMonitorList[0][0]
        Local $aInfo = _Monitor_GetInfo($i)
        If @error Then ContinueLoop

        Local $aSettings = _Monitor_GetDisplaySettings($i)
        Local $sResolution = @error ? "N/A" : $aSettings[0] & "x" & $aSettings[1] & " @" & $aSettings[3] & "Hz"

        $sMsg &= StringFormat("+ Monitor %d: %s%s\n", $i, $aInfo[9] ? "(Primary) " : "", $aInfo[10])
        $sMsg &= StringFormat("  Bounds: L=%d, T=%d, R=%d, B=%d (%dx%d)\n", _
                $aInfo[1], $aInfo[2], $aInfo[3], $aInfo[4], _
                $aInfo[3] - $aInfo[1], $aInfo[4] - $aInfo[2])
        $sMsg &= StringFormat("  Work Area: L=%d, T=%d, R=%d, B=%d (%dx%d)\n", _
                $aInfo[5], $aInfo[6], $aInfo[7], $aInfo[8], _
                $aInfo[7] - $aInfo[5], $aInfo[8] - $aInfo[6])
        $sMsg &= "  Resolution: " & $sResolution & @CRLF & @CRLF
    Next
    ConsoleWrite($sMsg)
    If $bShowMsgBox Then MsgBox(64 + 262144, "Monitor Information", $sMsg, $iTimeout)
    Return $sMsg
EndFunc   ;==>_Monitor_ShowInfo

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_Refresh
; Description....: Refresh the monitor list by reloading information from the system
; Syntax.........: _Monitor_Refresh()
; Parameters.....: None
; Return values..: Success     - Number of monitors detected (>= 1)
;                  Failure     - -1, sets @error = 1 (Refresh failed, _Monitor_GetList failed)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Resets the monitor list and re-enumerates all monitors from the system.
;                  Useful when monitors are hot-plugged or display configuration changes.
;                  This forces a complete refresh of all monitor information.
; Related........: _Monitor_GetList, _Monitor_GetCount, _Monitor_IsConnected
; ================================================================================================================================
Func _Monitor_Refresh()
    ; Reset the list
    $__g_aMonitorList[0][0] = 0
    Local $iResult = _Monitor_GetList()
    If $iResult = -1 Then Return SetError(1, 0, -1)
    Return $iResult
EndFunc   ;==>_Monitor_Refresh

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_IsConnected
; Description....: Check if a monitor is still connected and its handle is still valid
; Syntax.........: _Monitor_IsConnected($iMonitor)
; Parameters.....: $iMonitor   - Monitor index (1..N)
; Return values..: Success     - True if monitor is connected and valid, False if disconnected
;                  Failure     - False, sets @error = 1 (Invalid monitor index)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Verifies that the monitor handle is still valid by querying GetMonitorInfo.
;                  Useful for detecting when a monitor has been unplugged or disconnected.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_Refresh, _Monitor_GetList, _Monitor_GetCount
; ================================================================================================================================
Func _Monitor_IsConnected($iMonitor)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, False)

    ; Check if monitor handle is still valid
    Local $hMonitor = $__g_aMonitorList[$iMonitor][0]
    ; _WinAPI_GetMonitorInfo is available from Windows 2000+ (compatible with XP)
    Local $aInfo = _WinAPI_GetMonitorInfo($hMonitor)
    Return (Not @error And IsArray($aInfo))
EndFunc   ;==>_Monitor_IsConnected

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetDPI
; Description....: Get DPI (Dots Per Inch) scaling information for a monitor
; Syntax.........: _Monitor_GetDPI($iMonitor)
; Parameters.....: $iMonitor   - Monitor index (1..N)
; Return values..: Success     - Array with 3 elements:
;                  |[0] - X DPI value
;                  |[1] - Y DPI value
;                  |[2] - Scaling percentage (typically 100, 125, 150, 175, 200, etc.)
;                  Failure     - 0, sets @error to non-zero:
;                  |@error = 1 - Invalid monitor index
;                  |@error = 2 - DPI query failed (fallback uses default 96 DPI)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Tries to use GetDpiForMonitor API (Windows 8.1+) for accurate DPI values.
;                  Falls back to GetDeviceCaps (Windows XP compatible) if GetDpiForMonitor is not available.
;                  On Windows XP/Vista/7/8, function uses GetDeviceCaps which works reliably.
;                  Scaling percentage is calculated as (DPI / 96) * 100.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
;                  Compatible with Windows XP SP2 and later.
; Related........: _Monitor_GetInfo, _Monitor_GetDisplaySettings, _Monitor_GetList
; ================================================================================================================================
Func _Monitor_GetDPI($iMonitor)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, 0)

    Local $hMonitor = $__g_aMonitorList[$iMonitor][0]
    Local $iDPI_X = 96, $iDPI_Y = 96

    ; Try to get DPI using GetDpiForMonitor (Windows 8.1+ only)
    ; Check OS version first to avoid loading shcore.dll on older systems
    If __Monitor_IsWindows8_1OrGreater() Then
        ; Check if shcore.dll exists before calling (Windows 8.1+)
        Local $hShCore = DllOpen("shcore.dll")
        If $hShCore <> -1 Then
            DllClose($hShCore)
            Local $aRet = DllCall("shcore.dll", "long", "GetDpiForMonitor", "handle", $hMonitor, "int", 0, "uint*", 0, "uint*", 0)
            If Not @error And IsArray($aRet) And $aRet[0] = 0 Then
                $iDPI_X = $aRet[3]
                $iDPI_Y = $aRet[4]
                Local $iScaling = Round(($iDPI_X / 96) * 100)
                Local $aResult[3] = [$iDPI_X, $iDPI_Y, $iScaling]
                Return $aResult
            EndIf
        EndIf
    EndIf

    ; Fallback: Use GetDeviceCaps (compatible with Windows XP and later)
    Local $hDC2 = DllCall("user32.dll", "handle", "GetDC", "hwnd", 0)
    If Not @error And IsArray($hDC2) And $hDC2[0] Then
        Local $aDPI_X = DllCall("gdi32.dll", "int", "GetDeviceCaps", "handle", $hDC2[0], "int", 88) ; LOGPIXELSX
        Local $aDPI_Y = DllCall("gdi32.dll", "int", "GetDeviceCaps", "handle", $hDC2[0], "int", 90) ; LOGPIXELSY
        If Not @error And IsArray($aDPI_X) And IsArray($aDPI_Y) And $aDPI_X[0] > 0 And $aDPI_Y[0] > 0 Then
            $iDPI_X = $aDPI_X[0]
            $iDPI_Y = $aDPI_Y[0]
        EndIf
        DllCall("user32.dll", "bool", "ReleaseDC", "hwnd", 0, "handle", $hDC2[0])
    EndIf

    Local $iScaling = Round(($iDPI_X / 96) * 100)
    Local $aResult[3] = [$iDPI_X, $iDPI_Y, $iScaling]
    Return $aResult
EndFunc   ;==>_Monitor_GetDPI

; #FUNCTION# ====================================================================================================================
; Name...........: _Monitor_GetOrientation
; Description....: Get the display orientation (rotation angle) for a monitor
; Syntax.........: _Monitor_GetOrientation($iMonitor)
; Parameters.....: $iMonitor   - Monitor index (1..N)
; Return values..: Success     - Orientation angle in degrees:
;                  |0   - Landscape (normal)
;                  |90  - Portrait (rotated 90° clockwise)
;                  |180 - Landscape flipped (rotated 180°)
;                  |270 - Portrait flipped (rotated 270° clockwise / 90° counter-clockwise)
;                  Failure     - -1, sets @error to non-zero:
;                  |@error = 1 - Invalid monitor index
;                  |@error = 2 - GetDisplaySettings failed (could not query display mode)
; Author.........: Dao Van Trong - TRONG.PRO
; Remarks........: Orientation is extracted from display mode flags.
;                  Most monitors typically return 0 (landscape) unless rotated in Windows display settings.
;                  Function automatically calls _Monitor_GetList() if monitor list is not initialized.
; Related........: _Monitor_GetDisplaySettings, _Monitor_GetInfo, _Monitor_GetList
; ================================================================================================================================
Func _Monitor_GetOrientation($iMonitor)
    If $__g_aMonitorList[0][0] = 0 Then _Monitor_GetList()
    If $iMonitor < 1 Or $iMonitor > $__g_aMonitorList[0][0] Then Return SetError(1, 0, -1)

    Local $aSettings = _Monitor_GetDisplaySettings($iMonitor)
    If @error Then Return SetError(2, 0, -1)

    ; DisplayMode field contains orientation information
    ; DM_DISPLAYORIENTATION values: 0=0°, 1=90°, 2=180°, 3=270°
    Local $iOrientation = 0
    If IsArray($aSettings) And UBound($aSettings) > 4 Then
        ; Check display mode flags for orientation
        Local $iDisplayMode = $aSettings[4]
        ; Orientation is stored in bits 8-9 of display mode
        $iOrientation = BitAND(BitShift($iDisplayMode, 8), 3) * 90
    EndIf

    Return $iOrientation
EndFunc   ;==>_Monitor_GetOrientation

EG:

; ==================================================================================================
; MonitorUDF_Examples.au3
; Interactive example tester for MonitorUDF.au3 UDF
; Fixed: Array index bug in FuncTest_10, improved error handling
; ==================================================================================================

#include <GUIConstantsEx.au3>
#include <ButtonConstants.au3>
#include <WindowsConstants.au3>
#include <GuiListBox.au3>
#include <GuiEdit.au3>
#include <Array.au3>
#include "Monitor_UDF.au3"  ; Updated include name

; ==================================================================================================
; Create GUI
; ==================================================================================================
Global $GUI_W = 860, $GUI_H = 600
Global $hGUI = GUICreate("MonitorUDF - Examples (by TRONG.PRO)", $GUI_W, $GUI_H, -1, -1)
GUISetBkColor(0xF5F5F5, $hGUI)

; Title
GUICtrlCreateLabel("MonitorUDF Example Launcher", 12, 10, 400, 24)
GUICtrlSetFont(-1, 12, 800, 0, 'Segoe UI', 5)

; Buttons column 1 - 6 buttons (1-6)
Local $x1 = 12, $y1 = 48, $bw = 260, $bh = 36, $gap = 8
Global $iBtn1 = GUICtrlCreateButton("1. Enumerate monitors", $x1, $y1 + ($bh + $gap) * 0, $bw, $bh)
Global $iBtn2 = GUICtrlCreateButton("2. Move Notepad -> Monitor #2 (center)", $x1, $y1 + ($bh + $gap) * 1, $bw, $bh)
Global $iBtn3 = GUICtrlCreateButton("3. Move Notepad -> Monitor #2 @ (100,100)", $x1, $y1 + ($bh + $gap) * 2, $bw, $bh)
Global $iBtn4 = GUICtrlCreateButton("4. Which monitor is mouse on?", $x1, $y1 + ($bh + $gap) * 3, $bw, $bh)
Global $iBtn5 = GUICtrlCreateButton("5. Show virtual desktop bounds", $x1, $y1 + ($bh + $gap) * 4, $bw, $bh)
Global $iBtn6 = GUICtrlCreateButton("6. Convert coords (local <-> virtual)", $x1, $y1 + ($bh + $gap) * 5, $bw, $bh)

; Buttons column 2 - 6 buttons (7-12)
Local $x2 = $x1 + $bw + 12
Global $iBtn7 = GUICtrlCreateButton("7. Show monitor info (MsgBox)", $x2, $y1 + ($bh + $gap) * 0, $bw, $bh)
Global $iBtn8 = GUICtrlCreateButton("8. Check if Notepad is visible", $x2, $y1 + ($bh + $gap) * 1, $bw, $bh)
Global $iBtn9 = GUICtrlCreateButton("9. Create small GUI on each monitor", $x2, $y1 + ($bh + $gap) * 2, $bw, $bh)
Global $iBtn10 = GUICtrlCreateButton("10. Move all visible windows -> primary", $x2, $y1 + ($bh + $gap) * 3, $bw, $bh)
Global $iBtn11 = GUICtrlCreateButton("11. Refresh monitor list", $x2, $y1 + ($bh + $gap) * 4, $bw, $bh)
Global $iBtn12 = GUICtrlCreateButton("12. Check monitor connected", $x2, $y1 + ($bh + $gap) * 5, $bw, $bh)

; Buttons column 3 - 6 buttons (13-17, but layout for 6)
Local $x3 = $x2 + $bw + 12
Global $iBtn13 = GUICtrlCreateButton("13. Get DPI scaling", $x3, $y1 + ($bh + $gap) * 0, $bw, $bh)
Global $iBtn14 = GUICtrlCreateButton("14. Get display orientation", $x3, $y1 + ($bh + $gap) * 1, $bw, $bh)
Global $iBtn15 = GUICtrlCreateButton("15. Enumerate display modes", $x3, $y1 + ($bh + $gap) * 2, $bw, $bh)
Global $iBtn16 = GUICtrlCreateButton("16. Get monitor from rect", $x3, $y1 + ($bh + $gap) * 3, $bw, $bh)
Global $iBtn17 = GUICtrlCreateButton("17. Get monitor from window", $x3, $y1 + ($bh + $gap) * 4, $bw, $bh)

; Controls: log edit, clear, auto-demo, close
Local $logX = 12, $logY = $y1 + ($bh + $gap) * 6
Local $logW = $GUI_W - 24, $logH = 180
Global $idLog = GUICtrlCreateEdit("", $logX, $logY, $logW, $logH, BitOR($ES_READONLY, $WS_HSCROLL, $WS_VSCROLL, $ES_MULTILINE))
GUICtrlSetFont($idLog, 9)
Global $idFuncTest__ClearLog = GUICtrlCreateButton("Clear Log", 12, $logY + $logH + 10, 120, 28)
Global $idFuncTest__RunAllDemo = GUICtrlCreateButton("Auto Demo (Run 1..17)", 150, $logY + $logH + 10, 220, 28)
Global $idFuncTest__Close = GUICtrlCreateButton("Close", $GUI_W - 120, $logY + $logH + 10, 100, 28)
Global $pidNotepad = 0
GUISetState(@SW_SHOW)

; Keep a list of created GUIs for Example 10 so we can close them later
Global $Msg, $g_createdGUIs[0]  ; FIXED: Initialize as empty array
Global $g_bAutoMode = False  ; Track if running in auto demo mode
Global $g_hNotepadWindows[0]  ; Track all notepad windows created

; ==================================================================================================
; Main loop
; ==================================================================================================
While 1
    $Msg = GUIGetMsg()
    Switch $Msg
        Case $GUI_EVENT_CLOSE, $idFuncTest__Close
            ; close any GUIs created in example 10
            For $i = 0 To UBound($g_createdGUIs) - 1
                If IsHWnd($g_createdGUIs[$i]) Then GUIDelete($g_createdGUIs[$i])
            Next
            ExitLoop
        Case $idFuncTest__ClearLog
            GUICtrlSetData($idLog, '', '')
        Case $idFuncTest__RunAllDemo
            $g_bAutoMode = True
            ReDim $g_hNotepadWindows[0]
            FuncTest_1()
            Sleep(1000)
            FuncTest_2()
            Sleep(2000)
            FuncTest_3()
            Sleep(2000)
            FuncTest_4()
            Sleep(1500)
            FuncTest_5()
            Sleep(1000)
            FuncTest_6()
            Sleep(1000)
            FuncTest_7()
            Sleep(1000)
            FuncTest_8()
            Sleep(2000)
            FuncTest_9()
            Sleep(1000)
            FuncTest_10()
            Sleep(1000)
            FuncTest_11()
            Sleep(1000)
            FuncTest_12()
            Sleep(1000)
            FuncTest_13()
            Sleep(1000)
            FuncTest_14()
            Sleep(1000)
            FuncTest_15()
            Sleep(1000)
            FuncTest_16()
            Sleep(1000)
            FuncTest_17()
            Sleep(2000)
            ; Cleanup all created windows
            _CleanupAllWindows()
            $g_bAutoMode = False

        Case $iBtn1
            FuncTest_1()
        Case $iBtn2
            FuncTest_2()
        Case $iBtn3
            FuncTest_3()
        Case $iBtn4
            FuncTest_4()
        Case $iBtn5
            FuncTest_5()
        Case $iBtn6
            FuncTest_6()
        Case $iBtn7
            FuncTest_7()
        Case $iBtn8
            FuncTest_8()
        Case $iBtn9
            FuncTest_9()
        Case $iBtn10
            FuncTest_10()
        Case $iBtn11
            FuncTest_11()
        Case $iBtn12
            FuncTest_12()
        Case $iBtn13
            FuncTest_13()
        Case $iBtn14
            FuncTest_14()
        Case $iBtn15
            FuncTest_15()
        Case $iBtn16
            FuncTest_16()
        Case $iBtn17
            FuncTest_17()
    EndSwitch
    Sleep(5)
WEnd

GUIDelete()
Exit 0

; ==================================================================================================
; Helper: append to log (with timestamp)
; ==================================================================================================
Func _Log($s)
    ConsoleWrite($s & @CRLF)
    ; Format: DD/MM/YYYY HH:MM:SS
    Local $sDate = StringFormat("%02d/%02d/%04d", @MDAY, @MON, @YEAR)
    Local $sTime = StringFormat("%02d:%02d:%02d", @HOUR, @MIN, @SEC)
    Local $t = $sDate & " " & $sTime
    Local $cur = GUICtrlRead($idLog)
    If $cur = "" Then
        GUICtrlSetData($idLog, "[" & $t & "] " & $s)
    Else
        GUICtrlSetData($idLog, $cur & @CRLF & "[" & $t & "] " & $s)
    EndIf
    ; move caret to end
    _GUICtrlEdit_LineScroll($idLog, 0, _GUICtrlEdit_GetLineCount($idLog))
EndFunc   ;==>_Log

; ==================================================================================================
; Helper: Cleanup all created windows (Notepad and GUIs)
; ==================================================================================================
Func _CleanupAllWindows()
    ; Close all Notepad windows
    Local $aList = WinList("[CLASS:Notepad]")
    For $i = 1 To $aList[0][0]
        If $aList[$i][0] <> "" Then
            Local $hWnd = WinGetHandle($aList[$i][0])
            If $hWnd Then WinClose($hWnd)
        EndIf
    Next
    Sleep(300)
    ; Force close any remaining Notepad processes
    While ProcessExists("notepad.exe")
        ProcessClose("notepad.exe")
        Sleep(100)
    WEnd
    ; Close all created GUIs
    For $i = 0 To UBound($g_createdGUIs) - 1
        If IsHWnd($g_createdGUIs[$i]) Then GUIDelete($g_createdGUIs[$i])
    Next
    ReDim $g_createdGUIs[0]
    ReDim $g_hNotepadWindows[0]
EndFunc   ;==>_CleanupAllWindows

; ==================================================================================================
; Helper: Track Notepad window
; ==================================================================================================
Func _TrackNotepadWindow($hWnd)
    If $hWnd Then
        Local $n = UBound($g_hNotepadWindows)
        ReDim $g_hNotepadWindows[$n + 1]
        $g_hNotepadWindows[$n] = $hWnd
    EndIf
EndFunc   ;==>_TrackNotepadWindow

Func FuncTest_1()
    _Log('+ TEST 1: Enumerate monitors -----------------------\')
    ; Enumerate monitors
    _Monitor_GetList()
    Local $cnt = _Monitor_GetCount()
    If @error Then
        Local $sMsg = "TEST 1: FAILED" & @CRLF & "ERROR - Failed to enumerate monitors" & @CRLF & "@error=" & @error
        _Log("---> Example 1: ERROR - Failed to enumerate monitors")
        If Not $g_bAutoMode Then MsgBox(48, "Example 1", $sMsg, 3)
        Return
    EndIf
    _Log("---> Example 1: Monitors detected: " & $cnt)
    Local $sResults = "Total Monitors: " & $cnt & @CRLF & @CRLF
    For $i = 1 To $cnt
        Local $a = _Monitor_GetInfo($i)
        If @error Then
            _Log("  Monitor " & $i & ": ERROR getting info")
            $sResults &= "Monitor #" & $i & ": ERROR" & @CRLF
        Else
            Local $sPrimary = $a[9] ? " [PRIMARY]" : ""
            _Log("  Monitor " & $i & ": Device=" & $a[10] & " Bounds=(" & $a[1] & "," & $a[2] & ")-(" & $a[3] & "," & $a[4] & ") Work=(" & $a[5] & "," & $a[6] & ")-(" & $a[7] & "," & $a[8] & ") Primary=" & $a[9])
            $sResults &= "Monitor #" & $i & $sPrimary & ": " & $a[10] & @CRLF & _
                    "  Bounds: " & $a[1] & "," & $a[2] & " to " & $a[3] & "," & $a[4] & @CRLF & _
                    "  Work Area: " & $a[5] & "," & $a[6] & " to " & $a[7] & "," & $a[8] & @CRLF
        EndIf
    Next
    Local $sMsg = "TEST 1: SUCCESS" & @CRLF & @CRLF & $sResults
    If Not $g_bAutoMode Then MsgBox(64, "Example 1", $sMsg, 8)
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_1

Func FuncTest_2()
    _Log('+ TEST 2: Move Notepad to monitor #2 centered ------\')
    ; Move Notepad to monitor #2 centered
    $pidNotepad = Run("notepad.exe")
    If Not WinWaitActive("[CLASS:Notepad]", "", 5) Then
        _Log("---> Example 2: Notepad did not start / focus")
        Local $sMsg = "TEST 2: FAILED" & @CRLF & "Notepad did not start / focus"
        If Not $g_bAutoMode Then MsgBox(48, "Example 2", $sMsg, 3)
    Else
        Sleep(1000)
        Local $hWnd = WinGetHandle("[CLASS:Notepad]")
        _TrackNotepadWindow($hWnd)
        _Monitor_GetList()
        Local $cnt = _Monitor_GetCount()
        If $cnt < 2 Then
            Local $sMsg = "TEST 2: SKIPPED" & @CRLF & "Need at least 2 monitors" & @CRLF & "Current: " & $cnt & " monitor(s)"
            _Log("---> Example 2: Need at least 2 monitors")
            If Not $g_bAutoMode Then MsgBox(64, "Example 2", $sMsg, 3)
        Else
            Local $iResult = _Monitor_MoveWindowToScreen("[CLASS:Notepad]", "", 2, -1, -1, True)
            If @error Then
                Local $sMsg = "TEST 2: FAILED" & @CRLF & "ERROR moving window" & @CRLF & "@error=" & @error
                _Log("---> Example 2: ERROR moving window: @error=" & @error)
                If Not $g_bAutoMode Then MsgBox(48, "Example 2", $sMsg, 3)
            Else
                Local $sMsg = "TEST 2: SUCCESS" & @CRLF & "Notepad moved to Monitor #2" & @CRLF & "Position: Centered"
                _Log("---> Example 2: Notepad moved to monitor #2 (centered)")
                If Not $g_bAutoMode Then MsgBox(64, "Example 2", $sMsg, 3)
            EndIf
        EndIf
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_2

Func FuncTest_3()
    _Log('+ TEST 3: Move Notepad to monitor #2 at (100,100 ----\)')
    ; Move Notepad to monitor #2 at (100,100)
    $pidNotepad = Run("notepad.exe")
    If Not WinWaitActive("[CLASS:Notepad]", "", 5) Then
        Local $sMsg = "TEST 3: FAILED" & @CRLF & "Notepad did not start / focus"
        _Log("---> Example 3: Notepad did not start / focus")
        If Not $g_bAutoMode Then MsgBox(48, "Example 3", $sMsg, 3)
    Else
        Sleep(1000)
        Local $hWnd = WinGetHandle("[CLASS:Notepad]")
        _TrackNotepadWindow($hWnd)
        _Monitor_GetList()
        Local $cnt = _Monitor_GetCount()
        If $cnt < 2 Then
            Local $sMsg = "TEST 3: SKIPPED" & @CRLF & "Need at least 2 monitors" & @CRLF & "Current: " & $cnt & " monitor(s)"
            _Log("---> Example 3: Need at least 2 monitors")
            If Not $g_bAutoMode Then MsgBox(64, "Example 3", $sMsg, 3)
        Else
            Local $iResult = _Monitor_MoveWindowToScreen("[CLASS:Notepad]", "", 2, 100, 100, True)
            If @error Then
                Local $sMsg = "TEST 3: FAILED" & @CRLF & "ERROR moving window" & @CRLF & "@error=" & @error
                _Log("---> Example 3: ERROR moving window: @error=" & @error)
                If Not $g_bAutoMode Then MsgBox(48, "Example 3", $sMsg, 3)
            Else
                Local $sMsg = "TEST 3: SUCCESS" & @CRLF & "Notepad moved to Monitor #2" & @CRLF & "Position: (100, 100)"
                _Log("---> Example 3: Notepad moved to monitor #2 at (100,100)")
                If Not $g_bAutoMode Then MsgBox(64, "Example 3", $sMsg, 3)
            EndIf
        EndIf
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_3

Func FuncTest_4()
    _Log('+ TEST 4: Which monitor is mouse on ------------------\')
    ; Automatically move mouse to each monitor and test
    _Monitor_GetList()
    Local $cnt = _Monitor_GetCount()
    If $cnt < 1 Then
        Local $sMsg = "TEST 4: SKIPPED" & @CRLF & "No monitors detected"
        _Log("---> Example 4: No monitors detected")
        If Not $g_bAutoMode Then MsgBox(64, "Example 4", $sMsg, 3)
    Else
        Local $aResults = ""
        Local $sCurrentPos = MouseGetPos()
        Local $iOrigX = $sCurrentPos[0], $iOrigY = $sCurrentPos[1]

        _Log("---> Example 4: Testing " & $cnt & " monitor(s)")
        For $i = 1 To $cnt
            ; Get monitor center
            Local $iLeft, $iTop, $iRight, $iBottom
            _Monitor_GetBounds($i, $iLeft, $iTop, $iRight, $iBottom)
            Local $iCenterX = $iLeft + ($iRight - $iLeft) / 2
            Local $iCenterY = $iTop + ($iBottom - $iTop) / 2

            ; Move mouse to center of monitor
            MouseMove($iCenterX, $iCenterY, 0)
            Sleep(200)

            ; Check which monitor mouse is on
            Local $m = _Monitor_GetFromPoint()
            If @error Then
                _Log("  Monitor " & $i & ": ERROR - @error=" & @error)
                $aResults &= "Monitor " & $i & ": ERROR" & @CRLF
            Else
                Local $aInfo = _Monitor_GetInfo($i)
                Local $sDevice = @error ? "N/A" : $aInfo[10]
                Local $sStatus = ($m = $i) ? "CORRECT" : "WRONG (detected #" & $m & ")"
                _Log("  Monitor " & $i & " (" & $sDevice & "): Mouse detected on #" & $m & " - " & $sStatus)
                $aResults &= "Monitor #" & $i & " (" & $sDevice & "): #" & $m & " - " & $sStatus & @CRLF
            EndIf
            Sleep(300)
        Next

        ; Restore original mouse position
        MouseMove($iOrigX, $iOrigY, 0)

        Local $sMsg = "TEST 4: COMPLETE" & @CRLF & @CRLF & "Mouse Position Test Results:" & @CRLF & $aResults
        _Log("---> Example 4: Test complete, mouse restored to original position")
        If Not $g_bAutoMode Then MsgBox(64, "Example 4", $sMsg, 5)
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_4

Func FuncTest_5()
    _Log('+ TEST 5: Virtual desktop bounds -----------------------\')
    ; Virtual desktop bounds
    Local $aV = _Monitor_GetVirtualBounds()
    If @error Then
        Local $sMsg = "TEST 5: FAILED" & @CRLF & "ERROR getting virtual bounds" & @CRLF & "@error=" & @error
        _Log("---> Example 5: ERROR getting virtual bounds: @error=" & @error)
        If Not $g_bAutoMode Then MsgBox(48, "Example 5", $sMsg, 3)
    Else
        Local $sMsg = "TEST 5: SUCCESS" & @CRLF & @CRLF & "Virtual Desktop Bounds:" & @CRLF & _
                "Left: " & $aV[0] & @CRLF & _
                "Top: " & $aV[1] & @CRLF & _
                "Width: " & $aV[2] & @CRLF & _
                "Height: " & $aV[3] & @CRLF & @CRLF & _
                "Right: " & ($aV[0] + $aV[2]) & @CRLF & _
                "Bottom: " & ($aV[1] + $aV[3])
        _Log("---> Example 5: Virtual bounds L=" & $aV[0] & " T=" & $aV[1] & " W=" & $aV[2] & " H=" & $aV[3])
        If Not $g_bAutoMode Then MsgBox(64, "Example 5", $sMsg, 5)
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_5

Func FuncTest_6()
    _Log('+ TEST 6: Convert coords example (local -> virtual -> back) --\')
    ; Convert coords example (local -> virtual -> back)
    _Monitor_GetList()
    Local $mon = _Monitor_GetCount()
    If $mon < 1 Then
        Local $sMsg = "TEST 6: SKIPPED" & @CRLF & "No monitors detected"
        _Log("---> Example 6: No monitors detected")
        If Not $g_bAutoMode Then MsgBox(64, "Example 6", $sMsg, 3)
    Else
        Local $sResults = ""
        For $i = 1 To $mon
            Local $xLocal = 50, $yLocal = 100
            Local $aV = _Monitor_ToVirtual($i, $xLocal, $yLocal)
            If @error Then
                _Log("---> Example 6: Monitor " & $i & " ERROR converting to virtual: @error=" & @error)
                $sResults &= "Monitor #" & $i & ": ERROR (to virtual)" & @CRLF
            Else
                Local $aBack = _Monitor_FromVirtual($i, $aV[0], $aV[1])
                If @error Then
                    _Log("---> Example 6: Monitor " & $i & " ERROR converting from virtual: @error=" & @error)
                    $sResults &= "Monitor #" & $i & ": ERROR (from virtual)" & @CRLF
                Else
                    Local $bMatch = (Abs($aBack[0] - $xLocal) < 1) And (Abs($aBack[1] - $yLocal) < 1)
                    _Log("---> Example 6: Mon " & $i & " local(" & $xLocal & "," & $yLocal & ") -> virtual(" & $aV[0] & "," & $aV[1] & ") -> back(" & $aBack[0] & "," & $aBack[1] & ")")
                    $sResults &= "Monitor #" & $i & ": (" & $xLocal & "," & $yLocal & ") -> (" & $aV[0] & "," & $aV[1] & ") -> (" & $aBack[0] & "," & $aBack[1] & ")" & _
                            ($bMatch ? " ✓" : " ✗") & @CRLF
                EndIf
            EndIf
        Next
        Local $sMsg = "TEST 6: COMPLETE" & @CRLF & @CRLF & "Coordinate Conversion Test:" & @CRLF & $sResults
        If Not $g_bAutoMode Then MsgBox(64, "Example 6", $sMsg, 5)
    EndIf
    _Log('- End --------------------------------------------------------/')
EndFunc   ;==>FuncTest_6

Func FuncTest_7()
    _Log('+ TEST 7: Show detailed info via MsgBox (calls UDF) ------\')
    ; Show detailed info via MsgBox (calls UDF)
    _Monitor_GetList()
    Local $sResult = _Monitor_ShowInfo(1, 8)
    If @error Then
        Local $sMsg = "TEST 7: FAILED" & @CRLF & "ERROR showing info" & @CRLF & "@error=" & @error
        _Log("---> Example 7: ERROR showing info: @error=" & @error)
        If Not $g_bAutoMode Then MsgBox(48, "Example 7", $sMsg, 3)
    Else
        Local $sMsg = "TEST 7: SUCCESS" & @CRLF & @CRLF & "Detailed monitor information displayed above."
        _Log("---> Example 7: _Monitor_ShowInfo() called successfully")
        If Not $g_bAutoMode Then MsgBox(64, "Example 7", $sMsg, 3)
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_7

Func FuncTest_8()
    _Log('+ TEST 8: Start notepad, check visible ---------------\')
    ; Start notepad, check visible
    $pidNotepad = Run("notepad.exe")
    If Not WinWaitActive("[CLASS:Notepad]", "", 5) Then
        Local $sMsg = "TEST 8: FAILED" & @CRLF & "Notepad did not start/focus"
        _Log("---> Example 8: Notepad did not start/focus")
        If Not $g_bAutoMode Then MsgBox(48, "Example 8", $sMsg, 3)
    Else
        Sleep(1000)
        Local $h = WinGetHandle("[CLASS:Notepad]")
        _TrackNotepadWindow($h)
        Local $b = _Monitor_IsVisibleWindow($h)
        If @error Then
            Local $sMsg = "TEST 8: FAILED" & @CRLF & "ERROR checking visibility" & @CRLF & "@error=" & @error
            _Log("---> Example 8: ERROR checking visibility: @error=" & @error)
            If Not $g_bAutoMode Then MsgBox(48, "Example 8", $sMsg, 3)
        Else
            Local $sTitle = WinGetTitle($h)
            Local $sMsg = "TEST 8: SUCCESS" & @CRLF & @CRLF & "Window: " & ($sTitle = "" ? "[No Title]" : $sTitle) & @CRLF & _
                    "Handle: " & $h & @CRLF & _
                    "Visible: " & ($b ? "YES ✓" : "NO ✗")
            _Log("---> Example 8: Notepad handle " & $h & " visible? " & ($b ? "Yes" : "No"))
            If Not $g_bAutoMode Then MsgBox(64, "Example 8", $sMsg, 3)
        EndIf
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_8

Func FuncTest_9()
    _Log('+ TEST 9: Create small GUI on each monitor -------------\')
    ; Create small GUI on each monitor
    _Monitor_GetList()
    ; close previously created
    For $i = 0 To UBound($g_createdGUIs) - 1
        If IsHWnd($g_createdGUIs[$i]) Then GUIDelete($g_createdGUIs[$i])
    Next
    ReDim $g_createdGUIs[0]  ; reset
    Local $created = 0
    For $i = 1 To _Monitor_GetCount()
        Local $a = _Monitor_GetInfo($i)
        If @error Then
            _Log("  Monitor " & $i & ": ERROR getting info")
            ContinueLoop
        EndIf
        Local $h = GUICreate("Monitor #" & $i & " - " & $a[10], 260, 120, $a[1] + 40, $a[2] + 40)
        GUICtrlCreateLabel("Monitor " & $i & ($a[9] ? " (Primary)" : ""), 10, 12, 240, 20)
        GUISetState(@SW_SHOW, $h)
        ; store to close later
        __ArrayAdd($g_createdGUIs, $h)
        $created += 1
    Next
    Local $sMsg = "TEST 9: SUCCESS" & @CRLF & @CRLF & "Created " & $created & " GUI window(s)" & @CRLF & @CRLF
    If $created > 0 Then
        $sMsg &= "One GUI created on each monitor:" & @CRLF
        For $i = 1 To _Monitor_GetCount()
            Local $a = _Monitor_GetInfo($i)
            If Not @error Then
                Local $sPrimary = $a[9] ? " [PRIMARY]" : ""
                $sMsg &= "  Monitor #" & $i & $sPrimary & ": " & $a[10] & @CRLF
            EndIf
        Next
        $sMsg &= @CRLF & "Windows will be closed when you close the launcher."
    Else
        $sMsg &= "No GUIs were created."
    EndIf
    _Log("---> Example 9: Created " & $created & " GUI(s) on monitors. Use Close to exit (they will be closed).")
    If Not $g_bAutoMode Then MsgBox(64, "Example 9", $sMsg, 5)
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_9

Func FuncTest_10()
    _Log('+ TEST 10: Move all visible windows to primary ----------\')
    ; Move all visible windows to primary
    _Monitor_GetList()
    Local $prim = _Monitor_GetPrimary()
    If $prim = 0 Or @error Then
        Local $sMsg = "TEST 10: FAILED" & @CRLF & "Primary monitor not found or error" & @CRLF & "@error=" & @error
        _Log("---> Example 10: Primary monitor not found or error")
        If Not $g_bAutoMode Then MsgBox(48, "Example 10", $sMsg, 3)
    Else
        Local $aList = WinList()
        Local $moved = 0
        Local $aInfo = _Monitor_GetInfo($prim)
        Local $sDevice = @error ? "N/A" : $aInfo[10]
        For $i = 1 To $aList[0][0]
            If $aList[$i][0] <> "" Then
                ; FIXED: Use correct array index - WinList()[i][0] is title, need to get handle from title
                Local $h = WinGetHandle($aList[$i][0])  ; FIXED: Changed from [1] to [0]
                If Not @error And $h Then
                    If _Monitor_IsVisibleWindow($h) Then
                        Local $iResult = _Monitor_MoveWindowToScreen($h, "", $prim)
                        If Not @error Then $moved += 1
                    EndIf
                EndIf
            EndIf
        Next
        Local $sMsg = "TEST 10: SUCCESS" & @CRLF & @CRLF & _
                "Moved " & $moved & " visible window(s)" & @CRLF & _
                "to Primary Monitor #" & $prim & @CRLF & _
                "Device: " & $sDevice
        _Log("---> Example 10: Moved " & $moved & " visible windows to primary monitor #" & $prim)
        If Not $g_bAutoMode Then MsgBox(64, "Example 10", $sMsg, 4)
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_10

Func FuncTest_11()
    _Log('+ TEST 11: Refresh monitor list ----------------------\')
    ; Refresh monitor list
    Local $cntBefore = _Monitor_GetCount()
    _Log("---> Example 11: Monitors before refresh: " & $cntBefore)

    Local $cntAfter = _Monitor_Refresh()
    If @error Then
        Local $sMsg = "TEST 11: FAILED" & @CRLF & "ERROR refreshing monitor list" & @CRLF & "@error=" & @error
        _Log("---> Example 11: ERROR refreshing: @error=" & @error)
        If Not $g_bAutoMode Then MsgBox(48, "Example 11", $sMsg, 3)
    Else
        Local $sChangeInfo = ""
        If $cntBefore <> $cntAfter Then
            _Log("  --> Monitor count changed! (was " & $cntBefore & ", now " & $cntAfter & ")")
            $sChangeInfo = @CRLF & "⚠ CHANGE DETECTED! ⚠" & @CRLF & "Before: " & $cntBefore & " monitor(s)" & @CRLF & "After: " & $cntAfter & " monitor(s)"
        Else
            _Log("  --> Monitor count unchanged")
            $sChangeInfo = @CRLF & "Count unchanged: " & $cntAfter & " monitor(s)"
        EndIf
        Local $sMsg = "TEST 11: SUCCESS" & @CRLF & @CRLF & "Monitor list refreshed" & $sChangeInfo
        _Log("---> Example 11: Monitors after refresh: " & $cntAfter)
        If Not $g_bAutoMode Then MsgBox(64, "Example 11", $sMsg, 4)
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_11

Func FuncTest_12()
    _Log('+ TEST 12: Check if monitors are connected -----------\')
    ; Check if monitors are still connected
    _Monitor_GetList()
    Local $cnt = _Monitor_GetCount()
    If $cnt < 1 Then
        Local $sMsg = "TEST 12: SKIPPED" & @CRLF & "No monitors detected"
        _Log("---> Example 12: No monitors detected")
        If Not $g_bAutoMode Then MsgBox(64, "Example 12", $sMsg, 3)
    Else
        _Log("---> Example 12: Checking connection status for " & $cnt & " monitor(s):")
        Local $sResults = ""
        Local $iConnected = 0, $iDisconnected = 0
        For $i = 1 To $cnt
            Local $bConnected = _Monitor_IsConnected($i)
            If @error Then
                _Log("  Monitor " & $i & ": ERROR checking connection: @error=" & @error)
                $sResults &= "Monitor #" & $i & ": ERROR" & @CRLF
            Else
                Local $sStatus = $bConnected ? "CONNECTED ✓" : "DISCONNECTED ✗"
                Local $aInfo = _Monitor_GetInfo($i)
                Local $sDevice = @error ? "N/A" : $aInfo[10]
                _Log("  Monitor " & $i & " (" & $sDevice & "): " & ($bConnected ? "CONNECTED" : "DISCONNECTED"))
                $sResults &= "Monitor #" & $i & " (" & $sDevice & "): " & $sStatus & @CRLF
                If $bConnected Then
                    $iConnected += 1
                Else
                    $iDisconnected += 1
                EndIf
            EndIf
        Next
        Local $sMsg = "TEST 12: COMPLETE" & @CRLF & @CRLF & "Connection Status:" & @CRLF & _
                "Connected: " & $iConnected & @CRLF & _
                "Disconnected: " & $iDisconnected & @CRLF & @CRLF & _
                $sResults
        If Not $g_bAutoMode Then MsgBox(64, "Example 12", $sMsg, 5)
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_12

Func FuncTest_13()
    _Log('+ TEST 13: Get DPI scaling for monitors -------------\')
    ; Get DPI scaling for each monitor
    _Monitor_GetList()
    Local $cnt = _Monitor_GetCount()
    If $cnt < 1 Then
        Local $sMsg = "TEST 13: SKIPPED" & @CRLF & "No monitors detected"
        _Log("---> Example 13: No monitors detected")
        If Not $g_bAutoMode Then MsgBox(64, "Example 13", $sMsg, 3)
    Else
        _Log("---> Example 13: DPI information for " & $cnt & " monitor(s):")
        Local $sResults = ""
        For $i = 1 To $cnt
            Local $aDPI = _Monitor_GetDPI($i)
            If @error Then
                _Log("  Monitor " & $i & ": ERROR getting DPI: @error=" & @error)
                $sResults &= "Monitor #" & $i & ": ERROR" & @CRLF
            Else
                Local $aInfo = _Monitor_GetInfo($i)
                Local $sDevice = @error ? "N/A" : $aInfo[10]
                _Log("  Monitor " & $i & " (" & $sDevice & "):")
                _Log("    DPI X: " & $aDPI[0] & ", DPI Y: " & $aDPI[1])
                _Log("    Scaling: " & $aDPI[2] & "%")
                $sResults &= "Monitor #" & $i & " (" & $sDevice & "):" & @CRLF & _
                        "  DPI X: " & $aDPI[0] & ", DPI Y: " & $aDPI[1] & @CRLF & _
                        "  Scaling: " & $aDPI[2] & "%" & @CRLF
            EndIf
        Next
        Local $sMsg = "TEST 13: COMPLETE" & @CRLF & @CRLF & "DPI Information:" & @CRLF & $sResults
        If Not $g_bAutoMode Then MsgBox(64, "Example 13", $sMsg, 6)
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_13

Func FuncTest_14()
    _Log('+ TEST 14: Get display orientation -------------------\')
    ; Get display orientation for each monitor
    _Monitor_GetList()
    Local $cnt = _Monitor_GetCount()
    If $cnt < 1 Then
        Local $sMsg = "TEST 14: SKIPPED" & @CRLF & "No monitors detected"
        _Log("---> Example 14: No monitors detected")
        If Not $g_bAutoMode Then MsgBox(64, "Example 14", $sMsg, 3)
    Else
        _Log("---> Example 14: Display orientation for " & $cnt & " monitor(s):")
        Local $sOrientationNames[4] = ["Landscape (0°)", "Portrait (90°)", "Landscape Flipped (180°)", "Portrait Flipped (270°)"]
        Local $sResults = ""
        For $i = 1 To $cnt
            Local $iOrientation = _Monitor_GetOrientation($i)
            If @error Then
                _Log("  Monitor " & $i & ": ERROR getting orientation: @error=" & @error)
                $sResults &= "Monitor #" & $i & ": ERROR" & @CRLF
            Else
                Local $aInfo = _Monitor_GetInfo($i)
                Local $sDevice = @error ? "N/A" : $aInfo[10]
                Local $sOrientationName = "Unknown"
                If $iOrientation >= 0 And $iOrientation <= 270 Then
                    Local $iIndex = Int($iOrientation / 90)
                    If $iIndex >= 0 And $iIndex < 4 Then $sOrientationName = $sOrientationNames[$iIndex]
                EndIf
                _Log("  Monitor " & $i & " (" & $sDevice & "): " & $iOrientation & "° (" & $sOrientationName & ")")
                $sResults &= "Monitor #" & $i & " (" & $sDevice & "): " & $iOrientation & "°" & @CRLF & _
                        "  " & $sOrientationName & @CRLF
            EndIf
        Next
        Local $sMsg = "TEST 14: COMPLETE" & @CRLF & @CRLF & "Display Orientation:" & @CRLF & $sResults
        If Not $g_bAutoMode Then MsgBox(64, "Example 14", $sMsg, 5)
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_14

Func FuncTest_15()
    _Log('+ TEST 15: Enumerate all display modes ---------------\')
    ; Enumerate all display modes for all monitors (auto mode) or selected monitor
    _Monitor_GetList()
    Local $cnt = _Monitor_GetCount()
    If $cnt < 1 Then
        Local $sMsg = "TEST 15: SKIPPED" & @CRLF & "No monitors detected"
        _Log("---> Example 15: No monitors detected")
        If Not $g_bAutoMode Then MsgBox(64, "Example 15", $sMsg, 3)
    Else
        Local $sAllResults = ""
        Local $iTotalModes = 0

        ; In auto mode, test all monitors. Otherwise, ask user
        Local $bTestAll = $g_bAutoMode
        Local $aTestMonitors[1] = [1]  ; Default to monitor 1

        If Not $bTestAll And $cnt > 1 Then
            Local $sInput = InputBox("Example 15", "Select monitor to test (1-" & $cnt & ") or 0 for all:", "0", "", 250, 150)
            If Not @error And StringIsDigit($sInput) Then
                Local $iInput = Int($sInput)
                If $iInput = 0 Then
                    $bTestAll = True
                ElseIf $iInput >= 1 And $iInput <= $cnt Then
                    ; Test single monitor
                    $bTestAll = False
                    $aTestMonitors[0] = $iInput
                EndIf
            EndIf
        EndIf

        ; If testing all, create array of all monitor indices
        If $bTestAll Then
            ReDim $aTestMonitors[$cnt]
            For $i = 0 To $cnt - 1
                $aTestMonitors[$i] = $i + 1
            Next
        EndIf

        ; Test each monitor
        For $iMonitorIndex = 0 To UBound($aTestMonitors) - 1
            Local $iTestMonitor = $aTestMonitors[$iMonitorIndex]

            Local $aModes = _Monitor_EnumAllDisplayModes($iTestMonitor)
            If @error Then
                _Log("---> Example 15: Monitor " & $iTestMonitor & " ERROR enumerating modes: @error=" & @error)
                $sAllResults &= "Monitor #" & $iTestMonitor & ": ERROR" & @CRLF
            Else
                Local $aInfo = _Monitor_GetInfo($iTestMonitor)
                Local $sDevice = @error ? "N/A" : $aInfo[10]
                $iTotalModes += $aModes[0][0]
                _Log("---> Example 15: Monitor " & $iTestMonitor & " (" & $sDevice & ") has " & $aModes[0][0] & " display mode(s):")

                Local $sModesList = ""
                Local $iShowCount = ($aModes[0][0] > 5) ? 5 : $aModes[0][0]
                For $i = 1 To $iShowCount
                    Local $sModeInfo = $aModes[$i][0] & "x" & $aModes[$i][1] & " @ " & $aModes[$i][3] & "Hz, " & $aModes[$i][2] & " bpp"
                    _Log("  Mode " & $i & ": " & $sModeInfo)
                    $sModesList &= "  " & $sModeInfo & @CRLF
                Next
                If $aModes[0][0] > 5 Then
                    _Log("  ... and " & ($aModes[0][0] - 5) & " more mode(s)")
                    $sModesList &= "  ... and " & ($aModes[0][0] - 5) & " more mode(s)" & @CRLF
                EndIf

                If $bTestAll Then
                    $sAllResults &= "Monitor #" & $iTestMonitor & " (" & $sDevice & "): " & $aModes[0][0] & " modes" & @CRLF & $sModesList
                Else
                    $sAllResults = "Monitor #" & $iTestMonitor & " (" & $sDevice & "): " & $aModes[0][0] & " modes" & @CRLF & $sModesList
                EndIf
            EndIf
        Next

        Local $sMsg = "TEST 15: COMPLETE" & @CRLF & @CRLF
        If $bTestAll Then
            $sMsg &= "Total Modes Found: " & $iTotalModes & @CRLF & @CRLF
        EndIf
        $sMsg &= "Display Modes:" & @CRLF & $sAllResults

        If Not $g_bAutoMode Then MsgBox(64, "Example 15", $sMsg, 10)
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_15

Func FuncTest_16()
    _Log('+ TEST 16: Get monitor from rectangle ----------------\')
    ; Get monitor that overlaps with a rectangle
    _Monitor_GetList()
    Local $cnt = _Monitor_GetCount()
    If $cnt < 1 Then
        Local $sMsg = "TEST 16: SKIPPED" & @CRLF & "No monitors detected"
        _Log("---> Example 16: No monitors detected")
        If Not $g_bAutoMode Then MsgBox(64, "Example 16", $sMsg, 3)
    Else
        ; Create a test rectangle (center of primary monitor)
        Local $prim = _Monitor_GetPrimary()
        If $prim = 0 Then $prim = 1

        Local $iLeft, $iTop, $iRight, $iBottom
        _Monitor_GetBounds($prim, $iLeft, $iTop, $iRight, $iBottom)

        Local $iCenterX = $iLeft + ($iRight - $iLeft) / 2
        Local $iCenterY = $iTop + ($iBottom - $iTop) / 2
        Local $iRectW = 200, $iRectH = 150

        Local $iRectLeft = $iCenterX - $iRectW / 2
        Local $iRectTop = $iCenterY - $iRectH / 2
        Local $iRectRight = $iCenterX + $iRectW / 2
        Local $iRectBottom = $iCenterY + $iRectH / 2

        _Log("---> Example 16: Testing rectangle: L=" & $iRectLeft & ", T=" & $iRectTop & ", R=" & $iRectRight & ", B=" & $iRectBottom)

        Local $iMonitor = _Monitor_GetFromRect($iRectLeft, $iRectTop, $iRectRight, $iRectBottom)
        If @error Then
            Local $sMsg = "TEST 16: FAILED" & @CRLF & "ERROR getting monitor from rect" & @CRLF & "@error=" & @error
            _Log("---> Example 16: ERROR getting monitor from rect: @error=" & @error)
            If Not $g_bAutoMode Then MsgBox(48, "Example 16", $sMsg, 3)
        Else
            If $iMonitor > 0 Then
                Local $aInfo = _Monitor_GetInfo($iMonitor)
                Local $sDevice = @error ? "N/A" : $aInfo[10]
                Local $sPrimary = @error ? "" : ($aInfo[9] ? " [PRIMARY]" : "")
                Local $sMsg = "TEST 16: SUCCESS" & @CRLF & @CRLF & _
                        "Test Rectangle:" & @CRLF & _
                        "  Left: " & $iRectLeft & ", Top: " & $iRectTop & @CRLF & _
                        "  Right: " & $iRectRight & ", Bottom: " & $iRectBottom & @CRLF & @CRLF & _
                        "Found Monitor: #" & $iMonitor & $sPrimary & @CRLF & _
                        "Device: " & $sDevice
                _Log("---> Example 16: Rectangle overlaps with Monitor #" & $iMonitor & " (" & $sDevice & ")")
                If Not $g_bAutoMode Then MsgBox(64, "Example 16", $sMsg, 4)
            Else
                Local $sMsg = "TEST 16: NO MATCH" & @CRLF & @CRLF & "Rectangle does not overlap with any monitor"
                _Log("---> Example 16: Rectangle does not overlap with any monitor")
                If Not $g_bAutoMode Then MsgBox(48, "Example 16", $sMsg, 3)
            EndIf
        EndIf
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_16

Func FuncTest_17()
    _Log('+ TEST 17: Get monitor from window -------------------\')
    ; Get monitor containing a specific window
    _Monitor_GetList()

    ; Try to find Notepad or use current active window
    Local $hWnd = WinGetHandle("[CLASS:Notepad]")
    If @error Or Not $hWnd Then
        $hWnd = WinGetHandle("[ACTIVE]")
        If @error Or Not $hWnd Then
            _Log("---> Example 17: No suitable window found. Opening Notepad...")
            $pidNotepad = Run("notepad.exe")
            If WinWaitActive("[CLASS:Notepad]", "", 3) Then
                Sleep(500)
                $hWnd = WinGetHandle("[CLASS:Notepad]")
            Else
                _Log("---> Example 17: ERROR - Could not find or create test window")
                Return
            EndIf
        EndIf
    EndIf

    If $hWnd Then
        Local $sTitle = WinGetTitle($hWnd)
        _Log("---> Example 17: Testing window: " & ($sTitle = "" ? "[No Title]" : $sTitle) & " (Handle: " & $hWnd & ")")

        Local $iMonitor = _Monitor_GetFromWindow($hWnd)
        If @error Then
            _Log("---> Example 17: ERROR getting monitor from window: @error=" & @error)
        Else
            If $iMonitor > 0 Then
                Local $aInfo = _Monitor_GetInfo($iMonitor)
                Local $sDevice = @error ? "N/A" : $aInfo[10]
                Local $sPrimary = @error ? "" : ($aInfo[9] ? " [PRIMARY]" : "")
                _Log("---> Example 17: Window is on Monitor #" & $iMonitor & " (" & $sDevice & ")" & $sPrimary)

                ; Get window position for verification
                Local $aWinPos = WinGetPos($hWnd)
                If Not @error Then
                    _Log("  Window position: X=" & $aWinPos[0] & ", Y=" & $aWinPos[1])
                    _Log("  Monitor bounds: L=" & $aInfo[1] & ", T=" & $aInfo[2] & ", R=" & $aInfo[3] & ", B=" & $aInfo[4])
                EndIf

                Local $sMsg = "TEST 17: SUCCESS" & @CRLF & @CRLF & _
                        "Window: " & ($sTitle = "" ? "[No Title]" : $sTitle) & @CRLF & _
                        "Handle: " & $hWnd & @CRLF & @CRLF & _
                        "Monitor: #" & $iMonitor & $sPrimary & @CRLF & _
                        "Device: " & $sDevice & @CRLF & @CRLF & _
                        "Window Position:" & @CRLF & _
                        "  X: " & $aWinPos[0] & ", Y: " & $aWinPos[1]
                If Not $g_bAutoMode Then MsgBox(64, "Example 17", $sMsg, 5)
            Else
                _Log("---> Example 17: Window is not on any monitor")
            EndIf
        EndIf
    EndIf
    _Log('- End ------------------------------------------------/')
EndFunc   ;==>FuncTest_17

; ==================================================================================================
; Small helper to push item into dynamic array (simple)
; ==================================================================================================
Func __ArrayAdd(ByRef $a, $v)
    Local $n = 0
    If IsArray($a) Then $n = UBound($a)
    ReDim $a[$n + 1]
    $a[$n] = $v
EndFunc   ;==>__ArrayAdd

 

 

Edited by Trong
New version!

Enjoy my work? Buy me a 🍻 or tip via ❤️ PayPal

Posted
16 hours ago, Trong said:
_Monitor_MoveWindowToAll

In this function here, does it display the same GUI window identically on all monitors?

Or does it split up the GUI window evenly so that different parts on the window are visible on each monitor?

Posted
12 hours ago, WildByDesign said:

In this function here, does it display the same GUI window identically on all monitors?

Or does it split up the GUI window evenly so that different parts on the window are visible on each monitor?

NO, It just moves the GUI to another screen one by one (Used for testing or resolution change or screen change functionality)
The only way to “show on all screens at once” is:
Create multiple GUIs (windows) — one GUI for each screen.

Enjoy my work? Buy me a 🍻 or tip via ❤️ PayPal

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
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...