#include-once #include #include #include ; version 3 (Feb 25, 2026) ; Opt("MustDeclareVars", 1) ; commented out, as it may interfere with user environment. ;============================================== Func _SaveCoord($iStep = "?") ; 1 then 2 ; $aData : the two left columns will contain X and Y coords. Note how row 0 keeps count of rows in [0][0] & DPI in [0][1] Local Static $aData[1 + 30][10] = [ [0, _CheckDPI(), "Width", "Height", "Parent handle", "Handle", _ "Class name", "Control ID", "Title", "Style"] ] Local Static $bPhase1_Done Local $sCoordFile = StringTrimRight(@ScriptFullPath, 4) & ".coord" ; ".au3" | ".a3x" | ".exe" => ".coord" (in case user got an .ini file) Switch $iStep Case 1 Local $iRet = _Phase1($aData, $sCoordFile) If $iRet Then $bPhase1_Done = True Case 2 If Not $bPhase1_Done Then Return _Phase2($aData, $sCoordFile) Case Else MsgBox($MB_TOPMOST, "_SaveCoord(" & $iStep & ")", _ "The parameter must be 1 or 2 :" & @crlf & @crlf & _ "1 - when called just before showing the GUI (i.e. before GUISetState)" & @crlf & _ "2 - when called just before deleting the GUI (usually when script ends)") EndSwitch EndFunc ;==>_SaveCoord ;============================================== Func _Phase1(ByRef $aData, $sCoordFile) Local $aEnumTopLevel = _WinAPI_EnumThreadWindows(0, False) ; Enum. all non-child windows of this thread. False retrieves also hidden windows If @error Then MsgBox($MB_TOPMOST, "_WinAPI_EnumThreadWindows", "@error = " & @error) ; error 11 if no top-level window enumerated but... Return ; ...impossible (AutoIt internal window always exists) EndIf For $i = 1 To $aEnumTopLevel[0][0] ; row 0 contains count in [0][0], always >= 1 (or @error a few lines above, when 0) If $aEnumTopLevel[$i][1] = "AutoIt v3 GUI" Then ; top-level window (GUI, popup, MDI, owned... : all these don't have $WS_CHILD style) Local $hTopLevel = $aEnumTopLevel[$i][0] ; handle of an AutoIt GUI top-level window _AddToList($aData, $hTopLevel, "AutoIt v3 GUI", True) ; True means it is a top-level window Local $aEnumChild = _WinAPI_EnumChildWindows($hTopLevel, False) ; Enum. all child windows of this top-level window. False retrieves also hidden windows If @error Then ContinueLoop ; no child window found (seems possible, if a top-level window is created empty) For $j = 1 To $aEnumChild[0][0] _AddToList($aData, $aEnumChild[$j][0], $aEnumChild[$j][1], False) ; False means it is a child window Next EndIf Next If $aData[0][0] = 0 Then MsgBox($MB_TOPMOST, "_SaveCoord(1)", "No GUI has been found)") Return Else ReDim $aData[$aData[0][0] + 1][UBound($aData, $UBOUND_COLUMNS)] EndIf If FileExists($sCoordFile) Then Local $sOld = FileRead($sCoordFile) Local $aOld = _ArrayFromString($sOld) Local $iRet = _Phase1_CheckError($aData, $sCoordFile, $aOld) If Not $iRet Then Return Local $tPoint = DllStructCreate("int X;int Y") For $i = 1 To $aData[0][0] Select Case $aData[$i][7] == "Top-Level" ; == is important as 0 also found in same column : note how = would create issues +++ WinMove($aData[$i][5], "", $aOld[$i][0], $aOld[$i][1], $aOld[$i][2], $aOld[$i][3]) ; handle, "", X, Y, W, H Case Else ; child window (control or maybe a child GUI, grand child GUI too) : set coords X,Y relative to parent $tPoint.X = $aOld[$i][0] $tPoint.Y = $aOld[$i][1] DllCall("user32.dll", "bool", "ScreenToClient", "hwnd", $aData[$i][4], "struct*", $tPoint) ; _WinAPI_ScreenToClient If $aData[$i][7] > 2 And $aData[$i][7] < 10000 Then ; AutoIt control ID's start at 3 GUICtrlSetPos($aData[$i][7], $tPoint.X, $tPoint.Y, $aOld[$i][2], $aOld[$i][3]) ; ID, X, Y, W, H Else ; control >= 10000 (its handle created by UDF) or child GUI or ... WinMove($aData[$i][5], "", $tPoint.X, $tPoint.Y, $aOld[$i][2], $aOld[$i][3]) ; handle, "", X, Y, W, H EndIf EndSelect Next EndIf Return 1 EndFunc ;==>_Phase1 ;============================================== Func _Phase1_CheckError(ByRef $aData, $sCoordFile, ByRef $aOld) Local $sError Select Case $aData[0][0] <> $aOld[0][0] $sError = "Mismatch between sessions : number of components has changed." & @crlf & _ "Previous session : " & $aOld[0][0] & " components" & @crlf & _ "Current session : " & $aData[0][0] & " components" Case Else For $i = 1 To $aData[0][0] If $aData[$i][6] <> $aOld[$i][6] Or $aData[$i][7] <> $aOld[$i][7] Or $aData[$i][9] <> $aOld[$i][9] Then $sError = "Session mismatch : component at row " & $i & " has different properties" & @crlf & _ "Previous class name : " & $aOld[$i][6] & @crlf & _ "Current class name : " & $aData[$i][6] & @crlf & @crlf & _ "Previous control ID : " & $aOld[$i][7] & @crlf & _ "Current control ID : " & $aData[$i][7] & @crlf & @crlf & _ "Previous style : " & $aOld[$i][9] & @crlf & _ "Current style : " & $aData[$i][9] ExitLoop EndIf Next EndSelect If $sError Then MsgBox($MB_TOPMOST, "Discrepancy detected between previous and current session", _ $sError & @crlf & @crlf & _ "Coord file is inconsistent and will be deleted.") FileDelete($sCoordFile) Return EndIf Return 1 EndFunc ;==>_Phase1_CheckError ;============================================== Func _Phase2(ByRef $aData, $sCoordFile) Local $aPos For $i = 1 To $aData[0][0] ; which is always 1+ If $aData[$i][7] == "Top-Level" Then ; == is important as 0 also found in same column : note how = would create issues +++ If BitAND(WinGetState($aData[$i][5]), 16) Then ; $WIN_STATE_MINIMIZED = 16 (prevent -32000 coords) GuiSetState(@SW_SHOW, $aData[$i][5]) ; in case it was hidden (@SW_SHOW before @SW_RESTORE or -32000 coords saved in .coord file) GuiSetState(@SW_RESTORE, $aData[$i][5]) EndIf EndIf $aPos = WinGetPos($aData[$i][5]) ; all windows are saved with coords relative to screen (even controls) For $j = 0 To 3 ; X, Y, W, H $aData[$i][$j] = $aPos[$j] Next Next Local $hCoord = FileOpen($sCoordFile, 2) ; $FO_OVERWRITE = 2 If $hCoord = -1 Then MsgBox($MB_TOPMOST, "FileOpen error", $sCoordFile) Else FileWrite($hCoord, _ArrayToString($aData)) FileClose($hCoord) EndIf EndFunc ;==>_Phase2 ;============================================== Func _AddToList(ByRef $aData, $hWnd, $sClassName, $bTopLevel) $aData[0][0] += 1 If $aData[0][0] > UBound($aData) - 1 Then ReDim $aData[$aData[0][0] + 30][UBound($aData, $UBOUND_COLUMNS)] Local $iRow = $aData[0][0] ; 1+ ; [$iRow][0] To [$iRow][3] will be filled later (X, Y, W, H) in _Phase2 $aData[$iRow][4] = _WinAPI_GetAncestor($hWnd, $GA_PARENT) ; the real parent, as _WinAPI_GetParent may return an eventual owner handle $aData[$iRow][5] = $hWnd $aData[$iRow][6] = $sClassName $aData[$iRow][7] = $bTopLevel ? "Top-Level" : _WinAPI_GetDlgCtrlID($hWnd) $aData[$iRow][8] = WinGetTitle($hWnd) $aData[$iRow][9] = "0x" & Hex(_WinAPI_GetWindowLong($hWnd, $GWL_STYLE), 8) ; normalize 32/64 bits (64 bits returns 16 length with 8 leading 0's : tested) EndFunc ;==>_AddToList ;============================================== Func _CheckDPI() ; Read the current DPI scaling factor from Windows Local $iAppliedDPI = RegRead("HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics", "AppliedDPI") If @error then Return 1 ; assume 100% (though strange @error as the registry key is found on all OS's even the oldest ones) Local $iScaleFactor = $iAppliedDPI / 96 ; Display the scale factor (e.g. 1 for 100%, 1.25 for 125%, 1.5 for 150%) ; MsgBox($MB_TOPMOST, "DPI Scale", "Current DPI Scale Factor: " & $iScaleFactor) Return $iScaleFactor ; 1 or 1.25 or 1.50 etc... EndFunc ;==>_CheckDPI ;============================================== Func _WinAPI_EnumThreadWindows($iThreadId = 0, $bVisible = True) ; scripted (not found in AutoIt), with help from other AutoIt functions which use internal $__g_vEnum[][] and __Inc() If $iThreadId = 0 Then ; personal $iThreadId = DllCall("kernel32.dll", "dword", "GetCurrentThreadId")[0] ; _WinAPI_GetCurrentThreadId() EndIf Dim $__g_vEnum[101][2] = [[0]] Local $hEnumProc = DllCallbackRegister('__EnumWindowsProc', 'bool', 'hwnd;lparam') DllCall('user32.dll', 'bool', 'EnumThreadWindows', 'dword', $iThreadId, 'ptr', DllCallbackGetPtr($hEnumProc),'lparam', $bVisible) If @error Then $__g_vEnum = @error ; DllCall @error codes go from 1 to 5 . Note how the array $__g_vEnum[][] becomes an Int32 variable ElseIf Not $__g_vEnum[0][0] Then $__g_vEnum = 11 ; no top-level window enumerated EndIf DllCallbackFree($hEnumProc) If $__g_vEnum Then Return SetError($__g_vEnum, 0, 0) ; remember "If $__g_vEnum" is False when $__g_vEnum is an array __Inc($__g_vEnum, -1) ; -1 to Redim $__g_vEnum (based on the counter value found in [0][0] + 1 : this will delete all upper blank rows) Return $__g_vEnum EndFunc ;==>_WinAPI_EnumThreadWindows