Simple Window Manager (save and recall positions)

Post your working scripts, libraries and tools for AHK v1.1 and older
DRocks
Posts: 565
Joined: 08 May 2018, 10:20

Simple Window Manager (save and recall positions)

21 Oct 2019, 13:52

Here's a simple Window Manager script that helps me saving and recalling window positions using a separate file per computer name so you can have different setups.

How it works in short:
1. Quickly add new window titles using RegEx versatility
2. Reposition or update positions of window matching your titles list

More details:
Image
Screenshot showing when you press the hotkey to add or edit a window title, you have all the title info needed to be very specific but you can easily change the entry in a Gui.

The script repositions windows that you have added to your mySavedWindows object -
which can be easily done with a GUI that shows up on demand with a hotkey.
The advantage of adding specific windows like this is that, your saved window Title list will be the only ones evaluated and other windows not in the list are skipped so its very fast and convenient.

How to add and save a new window for the first time :
1. Press the hotkey: Ctrl+Windows+Alt+Right Mouse Button while holding down press LButton on the window you want to save and the GUI will show up.
2. Modify the window title RegEx to your needs or leave it as is shown in the GUI by default
3. Press the save button on the GUI

It will be saved in the Ini structure like so:
[i)mySavedWindows_ALEXWORKSTATION.ini - Bloc-notes ahk_class i)Notepad ahk_exe i)NOTEPAD.EXE]
x=2430
y=0
w=1000
h=1080
MinMax=0


How to reposition all saved windows:
1. Press the hotkey: Ctrl+Windows+Alt+Numpad0
(the script will loop through all saved window titles and if it exists currently, it will compare current and saved positions then move it if needed)

How to update all savedWindow positions that are in your mySavedWindows object :
1. Press the hotkey : Ctrl+Windows+Alt+Numpad1 and confirm that you want to update positions to the current positions.

Script :

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
SetTitleMatchMode, RegEx
#SingleInstance, Force
SetBatchLines, -1
SetWinDelay, -1
DetectHiddenWindows, On
Menu, Tray, Icon, % "Data\WindowManager.ico" ; <div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" 			    title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div>

/*
	;--------------------------------------------------------------------------------
	; Version := 0.11
	; Updated := 2019-10-21
	; Contributors: 
	; - Alexandre Desroches (a.k.a. DRocks)
	;--------------------------------------------------------------------------------
	
	0.11:
	- make ahk_exe processnames case insensitive when saved to file "i)" ProcessName
	
	0.10:
	- Code-cleanup: now only using one window object mySavedWindows to Save and Load window settings
	
	0.09:
	- Updated window settings Objects structure and usage
	- Prepare main object to remove the myWinTitlesArray obj which is too similar to the mySavedWindows obj
	- Added possibility to add window with hotkey ctrl+win+alt+RightClick + LeftClick on desired window
	
	0.08:
	- Added icon
	- Use of ByRef with myWinTitlesArray for better performance
	- Optimized the window settings object by: 
	> Renaming the object to mySavedWindows
	> Renaming the function that creates it to FileRead_WindowSettings_2_Object()
	- New draft implementation of a hotkey function to add a clicked window to our myWinTitlesArray.
	> See line 105 in the source code for details.
	> Its currently just showing the Gui but not yet adding the window as intended. To do.
	
	0.07:
	- FileRead_WindowSettings_2_Object() - Got rid of the repetitive "Title" key in mySavedWindows object. We have this info already and don't need to store it in this object
	- Clean-up SaveAllWindows() and RepositionWindows() hotkeys:: by merging all code inside the respective functions. This makes code clearer.
	- Added Static variables to Save and Reposition Windows Functions to clearly identify Normal, Minimized and Maximized states (0=Normal, -1=Minimized or 1=Maximized)
	- Added if (Current_MinMax != Maximized) Condition to Protect previous mySavedWindows positions when its currently maximized and we're saving mySavedWindows positions
	
	0.06:
	- SaveAllWindows() function updated to fit object method with only one file write (see sub items below)
	-> Update Super-Global mySavedWindows objects correctly inside the loop
	-> Got rid of previous method involving multiple IniWrites inside the loop (was up to 5 writes per Window Title)
	-> Added variable - "NewFileContentToWrite" - to gradually build the settings content including window pos changes
	
*/

;=====================================================================================================================================================================================
GLOBAL mySavedWindowsFileName := "mySavedWindows_" . A_ComputerName . ".ini"
GLOBAL mySavedWindowsFilePath := "Data\" . mySavedWindowsFileName
GLOBAL mySavedWindows := FileRead_WindowSettings_2_Object(mySavedWindowsFilePath)
;=====================================================================================================================================================================================

; GUI to save a new window
Gui, 2:New, 	,	Window Manager (RegEx WinTitle mode)
Gui, 2:Add, Edit,	vEditClickedWindowSettingsTitle   				w900
Gui, 2:Add, Edit,	vEditClickedWindowSettingsClass  				wp 
Gui, 2:Add, Edit, 	vEditClickedWindowSettingsProcess 				wp
w:=900/4
Gui, 2:Add, Button,	gGuiSaveDeleteButton	wp r2, Save window settings
Gui, 2:Add, Button, % "xm+"w*1.5 " w"w " gGuiSaveDeleteButton", Delete window settings
Gui, 2:Add, Button,	w%w% gOpenSettingsFileButton, Open settings file
RETURN ; End of Auto-Execute


;=====================================================================================================================================================================================
#if ; Context Sensitivity for Hotkeys = Always active
;=====================================================================================================================================================================================
;#[Window Management Hotkeys]

; Save Windows: Ctrl + Windows + Alt + 1 -------------------------------------------------------------------------
^#!Numpad1::SaveAllWindows(mySavedWindowsFilePath)	; Call SaveAllWindows()

; Reposition Windows: Ctrl + Windows + Alt + 0 -------------------------------------------------------------------
^#!Numpad0::RepositionWindows()					; Call RepositionWindows() with reference to Array

; Save a new window GUI
^#!RButton::
ShowAddWindowGui := 0
ToolTip, Click Left Button while holding Right Button down to save this window
While GetKeyState("RButton", "P") and (! ShowAddWindowGui) {
	if (GetKeyState("LButton", "P")) {
		
		; Get settings
		MouseGetPos,,, ClickedWindowHandle
		if (hWnd := WinActive("ahk_id "ClickedWindowHandle)) {
			WinGetTitle, ClickedTitle
			WinGetClass, ClickedTitleClass
			WinGet, ClickedTitleProcessName, ProcessName
			WinGetPos, Current_x, Current_y, Current_w, Current_h
			WinGet, Current_MinMax, MinMax
			
			; Show Settings in Gui
			GuiControl, 2:, EditClickedWindowSettingsTitle  , % "i)"ClickedTitle
			GuiControl, 2:, EditClickedWindowSettingsClass  , % "ahk_class i)"ClickedTitleClass
			GuiControl, 2:, EditClickedWindowSettingsProcess, % "ahk_exe i)"ClickedTitleProcessName
			
			; Allow Gui to show updated
			ShowAddWindowGui := 1
		}
	} else {
		Sleep, 10 ; Prevent useless CPU usage
	}
}
ToolTip ; off
if (ShowAddWindowGui)
	Gui, 2:Show
return


; Toggle Minimize all window / Undo
^Tab::
if (WinMinimizeAll_ToggleStatus = "") {
	WinMinimizeAll
	WinMinimizeAll_ToggleStatus:= "ON"
	Sleep, 500
} else if (WinMinimizeAll_ToggleStatus = "ON") {
	WinMinimizeAllUndo
	WinMinimizeAll_ToggleStatus:=
	Sleep, 500
}
return


; Close active window
^Escape::
WinClose, A		
Sleep, 700
return


; Minimize and remember last active
#WheelDown::
MouseGetPos,,,windowUnderCursor
WinGetTitle, winTitle, % "ahk_id "windowUnderCursor
if (!winTitle or winTitle="Program Manager")
	return
LastActiveWindow_Minimized:= WinExist("ahk_id "windowUnderCursor)
WinMinimize
Sleep, 400
return
#WheelUP::
if (!LastActiveWindow_Minimized)
	return
WinRestore, % "ahk_id "LastActiveWindow_Minimized
Sleep, 400
return


; Maximize and remember last active
!#WheelUP::
MouseGetPos,,,windowUnderCursor
WinGetTitle, winTitle, % "ahk_id "windowUnderCursor
if (!winTitle or winTitle="Program Manager")
	return
LastActiveWindow_Maximized:= WinExist("ahk_id "windowUnderCursor)
WinMaximize
Sleep, 800
return
!#WheelDown::
if (!LastActiveWindow_Maximized)
	return
WinRestore, % "ahk_id "LastActiveWindow_Maximized
Sleep, 800
return



;=====================================================================================================================================================================================
; Functions
;=====================================================================================================================================================================================

;-----------------------------------------------------------------------------------------------------------------------------------------
FileRead_WindowSettings_2_Object(ByRef mySavedWindowsFilePath) {
;-----------------------------------------------------------------------------------------------------------------------------------------
	obj := [] ; Initiate empty object before the loop builds it
	
   ; Temporarily save file's content in a variable for Parsing loop ---------------------
	FileRead, mySavedWindowsFile, %mySavedWindowsFilePath%
	Loop, Parse, mySavedWindowsFile, `n, `r	; Loop one Line at a time:
	{
	    ;===============================================================================
	    ; RegEx matches of file's content to build mySavedWindows[winTitle, ,,,] object
	    ;===============================================================================
		If (RegExMatch(A_LoopField, "^\[(?<Title>.*)\]$", matched)) { 												; Ex: [Title]
			winTitle := matchedTitle
			obj[winTitle]:=[] ; initiate sub object with wintile as key
		} 
		Else if (RegExMatch(A_LoopField, "^MinMax=(?<MinMax>-1|0|1)$", matched)) { ; Ex: Google Chrome
			obj[winTitle, "MinMax"] := matchedMinMax
		} 
		Else if (RegExMatch(A_LoopField, "^x=(?<X>-?\d{1,4})$"		 , matched)) { ; Ex: x= -10 / 0 / 1700
			obj[winTitle, "x"] := matchedX
		} 
		Else if (RegExMatch(A_LoopField, "^y=(?<Y>-?\d{1,4})$"		 , matched)) { ; Ex: y= -10 / 0 / 900
			obj[winTitle, "y"] := matchedY
		} 
		Else if (RegExMatch(A_LoopField, "^w=(?<W>\d{1,4})$"		 , matched)) { ; Ex: w= 0 / 1700
			obj[winTitle, "w"] := matchedW
		} 
		Else if (RegExMatch(A_LoopField, "^h=(?<H>\d{1,4})$"		 , matched)) { ; Ex: h= 0 / 900
			obj[winTitle, "h"] := matchedH
		}
	}
	
	Return, obj
}

;-----------------------------------------------------------------------------------------------------------------------------------------
SaveAllWindows(ByRef mySavedWindowsFilePath) { ; loop wintitles in array and save pos if currently exist
;-----------------------------------------------------------------------------------------------------------------------------------------
	CRITICAL
	LastActiveWindow := WinActive("A") ; Store active window handle to refocus it when done
	
   ; Request user confirmation ---------------------------------------------------------------------------
	MsgBox, 262180, Save Window Positions ?, Do you want to save the current window position ?`n`nIf yes, the %mySavedWindowsFilePath% file will be updated accordingly.
	IfMsgBox No
		return ; Stop here - user cancelled
   ;------------------------------------------------------------------------------------------------------
	
  ; Loop through Window Titles in Array and save them if exists
	For winTitle in mySavedWindows {
		winSettingsString := getSetWinSettingsObject(winTitle)
		NewFileContentToWrite .= winSettingsString
	}
	
	WinActivate, ahk_id %LastActiveWindow% ; Re-Activate the window that was active before repositionning
	
  ; Delete old file first then create new file including window settings changes
	FileDelete, %mySavedWindowsFilePath%
	FileAppend, %NewFileContentToWrite%, %mySavedWindowsFilePath%
}
;--------------------------------------------------------------------------------
getSetWinSettingsObject(ByRef winTitle) {
;--------------------------------------------------------------------------------
	Static Maximized:= 	1
		 , Normal:= 	0
		 , Minimized:= -1
	
	if (hWnd := WinExist(winTitle)) { ; "hWnd" holds this window's unique ID if it exists
		
	  ; Define File's saved Values from Super-Global "mySavedWindows" object:
		saved_MinMax := mySavedWindows[winTitle, "MinMax"]
	  , saved_x 	 := mySavedWindows[winTitle, "x"	 ]
	  , saved_y 	 := mySavedWindows[winTitle, "y"	 ]
	  , saved_w 	 := mySavedWindows[winTitle, "w"	 ]
	  , saved_h 	 := mySavedWindows[winTitle, "h"	 ]
		
	  ; What is the current Minimized / Normal / Maximized state of this window ?
		WinGet, Current_MinMax, MinMax, ahk_id %hWnd%
		if (Current_MinMax != saved_MinMax)
			mySavedWindows[winTitle, "MinMax"] := Current_MinMax
		if (Current_MinMax = Minimized)
			WinActivate, ahk_id %hWnd% 
		    ; it needs to be activated before getting positions, 
			; otherwise WinGetPos would return an error for x and y being equal to -32000 
		
		if (Current_MinMax != Maximized) { ; Protect previous saved pos when currently maximized
			WinGetPos, Current_x, Current_y, Current_w, Current_h, ahk_id %hWnd%
			if (Current_x != saved_x) or (Current_y != saved_y) or (Current_w != saved_w) or (Current_h != saved_h) {			
				mySavedWindows[winTitle, "x"] := Current_x
				mySavedWindows[winTitle, "y"] := Current_y
				mySavedWindows[winTitle, "w"] := Current_w
				mySavedWindows[winTitle, "h"] := Current_h
			}
		}
		
	  ; Does the window need to be put back to a initial Minimized or Maximized state ?
		if (Current_MinMax != Normal) {
			if (Current_MinMax = Minimized)
				WinMinimize
			else if (Current_MinMax = Maximized)
				WinMaximize
		}
	}
	
	winSettingsString :="[" winTitle "]"
					  . "`nx="mySavedWindows[winTitle, "x"]
					  . "`ny="mySavedWindows[winTitle, "y"]
					  . "`nw="mySavedWindows[winTitle, "w"]
					  . "`nh="mySavedWindows[winTitle, "h"]
					  . "`nMinMax="mySavedWindows[winTitle, "MinMax"]
					  . "`n"
	return winSettingsString
}

;-----------------------------------------------------------------------------------------------------------------------------------------
RepositionWindows() {
;-----------------------------------------------------------------------------------------------------------------------------------------
	Static Maximized:= 	1
		 , Normal:=  	0
		 , Minimized:= -1
	CRITICAL
	LastActiveWindow := WinActive("A")
	
	For winTitle in mySavedWindows {
		if (hWnd := WinExist(winTitle)) {
		  ; Get saved values from Super-Global window object
			saved_MinMax := mySavedWindows[winTitle, "MinMax"]
		  , saved_x		 := mySavedWindows[winTitle, "x"	 ]
		  , saved_y		 := mySavedWindows[winTitle, "y"	 ]
		  , saved_w		 := mySavedWindows[winTitle, "w"	 ]
		  , saved_h		 := mySavedWindows[winTitle, "h"	 ]
			
		  ; hWnd was set by WinExist(winTitle)
			WinGetPos, Current_x, Current_y, Current_w, Current_h, ahk_id %hWnd% 
			WinGet, Current_MinMax, MinMax, ahk_id %hWnd%
			
		  ; (Option A) - File's setting for this window says it needs to be [Minimized] or [Maximized]
			if (saved_MinMax != Normal) { ; 0 stands for Normal state
				
			  ; A-1 [Minimized] - File Setting says its supposed to be [Minimized] 
				if (saved_MinMax = Minimized) and (Current_MinMax != Minimized) {
					
					if (Current_x != saved_x) or (Current_y != saved_y) or (Current_w != saved_w) or (Current_h != saved_h) {
						WinRestore, ahk_id %hWnd%
						WinMove, ahk_id %hWnd%, , %saved_x%, %saved_y%, %saved_w%, %saved_h% 
					  ; Resized to match File's settings before [Minimizing]
					} 
					
					WinMinimize, ahk_id %hWnd%
					
				}
			  ; A-2 [Maximized] - File Setting says its supposed to be [Maximized]
				else if (saved_MinMax = Maximized) and (Current_MinMax != Maximized) {
					
					if (Current_x != saved_x) or (Current_y != saved_y) or (Current_w != saved_w) or (Current_h != saved_h) {
						WinRestore, ahk_id %hWnd%
						WinMove, ahk_id %hWnd%, , %saved_x%, %saved_y%, %saved_w%, %saved_h%
					  ; Resized to match File's settings before [Maximizing]
					}
					
					WinMaximize, ahk_id %hWnd%
					
				} 
			} 
			
		  ; (Option B) - File's setting for this window says it needs to be [Normal] - not Minimized and not Maximized.
			else if (saved_MinMax = Normal) { ; 0 stands for Normal state
				
			  ; B-1 [Normal] - File setting says it should be normal state and its already in normal state
				if (Current_MinMax = Normal) {
					
				  ; if current positions are different than what was saved in the File
					if (Current_x != saved_x) or (Current_y != saved_y) or (Current_w != saved_w) or (Current_h != saved_h) { 
						WinMove, ahk_id %hWnd%, , %saved_x%, %saved_y%, %saved_w%, %saved_h%
					}
					
				} 
			  ; B-1 [Normal] - File setting says it should be normal state and its currently NOT in normal state
				else if (Current_MinMax = Maximized) or (Current_MinMax = Minimized) { ; if Currently [Maximized] or [Minimized]
					
					WinRestore, ahk_id %hWnd% ; Need to restore before resizing window
					
					if (Current_x != saved_x) or (Current_y != saved_y) or (Current_w != saved_w) or (Current_h != saved_h) {
						WinMove, ahk_id %hWnd%, , %saved_x%, %saved_y%, %saved_w%, %saved_h%
					}
				}
			}
		}
	}
	WinActivate, ahk_id %LastActiveWindow% ; Re-Activate the window that was active before repositionning
}

;--------------------------------------------------------------------------------
OpenSettingsFileButton() {
;--------------------------------------------------------------------------------
	if !WinExist(mySavedWindowsFileName)
		Run, % mySavedWindowsFilePath
	else
		WinActivate
}

;--------------------------------------------------------------------------------
GuiSaveDeleteButton() {
;--------------------------------------------------------------------------------
	;== get GUI content =========================================================
	Global EditClickedWindowSettingsTitle
		 , EditClickedWindowSettingsClass
		 , EditClickedWindowSettingsProcess
	GuiControlGet, winTitle,2:,EditClickedWindowSettingsTitle
	GuiControlGet, class   ,2:,EditClickedWindowSettingsClass
	GuiControlGet, process ,2:,EditClickedWindowSettingsProcess
	winTitleString := Trim(winTitle " " class " " process)
	;============================================================================
	
	buttonAction := (InStr(A_GuiControl, "Save") ? "save":"delete")
	
	; check if window in saved settings
	For winTitle in mySavedWindows {
		if (winTitle = winTitleString) {
			if (buttonAction = "delete") {
				MsgBox,262209,Window Manager, % "Window found " 
				. " in your saved windows - delete saved settings for this window ?"
			} else {
				MsgBox,262209,Window Manager, % "Window already " 
				. " in your saved windows - this will update previously saved settings for this window."
			}
			IfMsgBox, Cancel
				return
			break
		}
	}
	
	winSettingsString := getSetWinSettingsObject(winTitleString)
	If (RegExMatch(winSettingsString, "^\[(?<Title>.*)\]", matched)) { 												; Ex: [Title]
		iniSection := matchedTitle
		winSettingsString := RegExReplace(winSettingsString, "\[.*\]`n")
		if (buttonAction = "delete")
			IniDelete, % mySavedWindowsFilePath, % iniSection
		else
			IniWrite, % winSettingsString, % mySavedWindowsFilePath, % iniSection
	} 
	
	Gui, 2:Hide
}
Download the .zip if you want to use the custom script icon.
Or remove script line 9 Menu, Tray... to avoid loading the custom Menu tray icon.
OneDrive Link - AHK-WindowManager_v0.11.zip
mmmax
Posts: 81
Joined: 25 Jun 2018, 09:01

Re: Simple Window Manager (save and recall positions)

15 Aug 2023, 17:00

Does anyone still have this? The OneDrive link is dead. ☹️
Oh never mind. I'll just comment out line 9 or use a different icon.
todsandberg
Posts: 2
Joined: 12 Aug 2019, 09:48

TIP when setting this up -- manually add folder for INIs to properly save

18 Oct 2023, 11:41

This is a great script. Thank you for writing it!

TIP to anyone using it...
* Before testing this script make a \Data folder for the INI to save the window locations into.

(create a subfolder called \Data in the folder where this script resides.)

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 272 guests