Simple Window Manager (save and recall positions)
Posted: 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:
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 :
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
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:
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
}
Or remove script line 9 Menu, Tray... to avoid loading the custom Menu tray icon.
OneDrive Link - AHK-WindowManager_v0.11.zip