#cs ----------------------------------------------------------------------------

	 AutoIt Version: 3.3.18.0
	 Author:         Kanashius

	 Script Function:
		Loop through windows

#ce ----------------------------------------------------------------------------
#include <WinAPIProc.au3>
#include <Array.au3>
#include <GuiListView.au3>
#include <WinAPISysWin.au3>
#include <TrayConstants.au3>
#include <ListViewConstants.au3>
#include <WindowsStylesConstants.au3>
#include <GUIConstantsEx.au3>
#include <StaticConstants.au3>
#include "ListViewEditInput.au3"

Opt("TrayMenuMode", 3)
Opt("TrayOnEventMode", 1)
Opt("GuiOnEventMode", 1)

Global $iDefaultIntervalMS = 5000, $sConfigFile = "config.ini"
Global $mWinLoopData[]
_init()

$mWinLoopData.idTrayGui = TrayCreateItem("Show Gui")
TrayItemSetOnEvent(-1, "_switchGui")
$mWinLoopData.idTrayPause = TrayCreateItem("Pause")
TrayItemSetOnEvent(-1, "_pauseLoop")
$mWinLoopData.idTrayAdd = TrayCreateItem("Enable window adding with ENTER")
TrayItemSetOnEvent(-1, "_switchAdding")
$mWinLoopData.idTrayExit = TrayCreateItem("Exit")
TrayItemSetOnEvent(-1, "_end")


; Local $mWinData = _winGetMap()
; _winMapDisplay($mWinData)
; Exit

; filter programms to switch
; Local $arFilterProgramms = [["vlc.exe", "", "", True], ["brave.exe", "", "Wikipedia", ""]]

; enable to show gui at the beginning
; _switchGui()

; iterate all windows to create initial state
_winListLoop(0, True)

While Sleep(10)
	; iterate all windows and show only one at a time
	If Not $mWinLoopData.bPauseLoop Then _winListLoop()
WEnd

Func _init()
	Local $mWindows[], $arWindows[0], $mFilter[]
	$mWinLoopData.bPauseLoop = False
	$mWinLoopData.bEnableAdding = False
	$mWinLoopData.iInterval = $iDefaultIntervalMS
	$mWinLoopData.mFilters = $mFilter
	$mWinLoopData.arWindows = $arWindows
	$mWinLoopData.mWindows = $mWindows
	_loadFromIni()
	_winListLoad()
EndFunc

Func _loadFromIni()
	If Not FileExists($sConfigFile) Then Return False
	Local $arSections = IniReadSectionNames($sConfigFile)
	Local $mFilters[]
	For $sSection in $arSections
		If StringInStr($sSection, "Filter-")=1 Then
			Local $mFilter[]
			$mFilter.sPName = IniRead($sConfigFile, $sSection, "ProcessName", "")
			$mFilter.sPNameL = StringLower($mFilter.sPName)
			$mFilter.sPPath = IniRead($sConfigFile, $sSection, "ProcessPath", "")
			$mFilter.sPPathL = StringLower($mFilter.sPPathL)
			$mFilter.sTitle = IniRead($sConfigFile, $sSection, "WindowTitle", "")
			$mFilter.sTitleL = StringLower($mFilter.sTitle)
			Local $bVisible = ""
			Local $sVisible = IniRead($sConfigFile, $sSection, "WindowVisible", "")
			If StringLower($sVisible)="true" Then $bVisible = True
			If StringLower($sVisible)="false" Then $bVisible = False
			$mFilter.bVisible = $bVisible
			Local $bDisabled = False
			Local $sDisabled = IniRead($sConfigFile, $sSection, "Disabled", False)
			If StringLower($sDisabled)="true" Then $bDisabled = True
			$mFilter.bDisabled = $bDisabled
			MapAppend($mFilters, $mFilter)
		ElseIf $sSection="General" Then
			$mWinLoopData.iInterval = IniRead($sConfigFile, $sSection, "Interval", $iDefaultIntervalMS)
		EndIf
	Next
	$mWinLoopData.mFilters = $mFilters
	Return True
EndFunc

Func _writeToIni()
	; delete old config
	Local $hFile = FileOpen($sConfigFile, 2)
	FileWrite($hFile, "")
	FileClose($hFile)
	; write new config
	IniWrite($sConfigFile, "General", "Interval", $mWinLoopData.iInterval)
	Local $arKeys = MapKeys($mWinLoopData.mFilters)
	_ArraySort($arKeys, 1) ; sort keys by int to keep order
	For $iKey in $arKeys
		Local $sSection = "Filter-"&$iKey
		Local $mFilter = $mWinLoopData.mFilters[$iKey]
		IniWrite($sConfigFile, $sSection, "ProcessName", $mFilter.sPName)
		IniWrite($sConfigFile, $sSection, "ProcessPath", $mFilter.sPPath)
		IniWrite($sConfigFile, $sSection, "WindowTitle", $mFilter.sTitle)
		IniWrite($sConfigFile, $sSection, "WindowVisible", String($mFilter.bVisible))
		IniWrite($sConfigFile, $sSection, "Disabled", $mFilter.bDisabled)
	Next
EndFunc

Func _switchAdding()
	$mWinLoopData.bEnableAdding = Not $mWinLoopData.bEnableAdding
	TrayItemSetText($mWinLoopData.idTrayAdd, $mWinLoopData.bEnableAdding?"Resume":"Pause")
	TrayItemSetState($mWinLoopData.idTrayAdd, $mWinLoopData.bEnableAdding?1:4)
	If MapExists($mWinLoopData, "idButtonToggleAdd") Then GUICtrlSetData($mWinLoopData.idButtonToggleAdd, $mWinLoopData.bEnableAdding?"🪟⦸":"🪟🞥")
	If $mWinLoopData.bEnableAdding Then
		HotKeySet("{ENTER}", "_winListAddActive")
	Else
		HotKeySet("{ENTER}")
	EndIf
EndFunc

Func _pauseLoop()
	$mWinLoopData.bPauseLoop = Not $mWinLoopData.bPauseLoop
	; ChrW(0x23F8) pause, ChrW(0x23F5) play
	TrayItemSetText($mWinLoopData.idTrayPause, $mWinLoopData.bPauseLoop?"Resume":"Pause")
	TrayItemSetState($mWinLoopData.idTrayPause, $mWinLoopData.bPauseLoop?1:4)
	If MapExists($mWinLoopData, "idButtonPause") Then GUICtrlSetData($mWinLoopData.idButtonPause, $mWinLoopData.bPauseLoop?ChrW(0x23F5):ChrW(0x23F8))
EndFunc

Func _end()
	; restore all windows
	_winListLoop(0, False)
	Exit
EndFunc

Func _switchGui()
	Local Static $hGui = 0
	If $hGui<>0 Then
		MapRemove($mWinLoopData, "hListViewWindows")
		GUIDelete($hGui)
		$hGui = 0
	Else
		Local $iWidth = 800, $iHeight = 600, $iSpace = 5, $iRightColWidth = 60, $iCtrlHeight = 35
		$hGui = GUICreate("Window switch manager", $iWidth, $iHeight)
		$mWinLoopData.hMainGui = $hGui
		GUISetOnEvent(-3, "_switchGui", $hGui)
		Local $iLeftColWidth = $iWidth - $iSpace*3 - $iRightColWidth
		$mWinLoopData.idListViewWindows = GUICtrlCreateListView("Process|Title", $iSpace, $iSpace, $iLeftColWidth, $iHeight-2*$iSpace, BitOR($LVS_EDITLABELS, $LVS_SHOWSELALWAYS), BitOR($LVS_EX_CHECKBOXES, $LVS_EX_FULLROWSELECT, $WS_EX_CLIENTEDGE))
		$mWinLoopData.hListViewWindows = GUICtrlGetHandle($mWinLoopData.idListViewWindows)
		_GUICtrlListView_SetColumnWidth($mWinLoopData.hListViewWindows, 0, $iLeftColWidth/4)
		_GUICtrlListView_SetColumnWidth($mWinLoopData.hListViewWindows, 1, ($iLeftColWidth/4)*3-10)
		Local $iLeft = $iLeftColWidth + $iSpace*2, $iTop = $iSpace
		GUISetFont(20)
		$mWinLoopData.idButtonPause = GUICtrlCreateButton(ChrW(0x23F8), $iLeft, $iTop, $iRightColWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_pauseLoop")
		$iTop += $iCtrlHeight + $iSpace
		$mWinLoopData.idButtonToggleAdd = GUICtrlCreateButton("🪟🞥", $iLeft, $iTop, $iRightColWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_switchAdding")
		$iTop += $iCtrlHeight + $iSpace
		$mWinLoopData.idButtonUp = GUICtrlCreateButton(ChrW(0x2B99), $iLeft, $iTop, $iRightColWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_winListSelectedUp")
		$iTop += $iCtrlHeight + $iSpace
		$mWinLoopData.idButtonDown = GUICtrlCreateButton(ChrW(0x2B9B), $iLeft, $iTop, $iRightColWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_winListSelectedDown")
		$iTop += $iCtrlHeight + $iSpace
		$mWinLoopData.idButtonRemove = GUICtrlCreateButton("🗑", $iLeft, $iTop, $iRightColWidth, $iCtrlHeight)
		GUICtrlSetFont(-1, 16)
		GUICtrlSetOnEvent(-1, "_winListRemoveSelected")
		$iTop += $iCtrlHeight + $iSpace
		$mWinLoopData.idButtonRefresh = GUICtrlCreateButton("🔄", $iLeft, $iTop, $iRightColWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_winListLoad")
		$iTop += $iCtrlHeight + $iSpace
		$mWinLoopData.idButtonSettings = GUICtrlCreateButton(ChrW(0x2699), $iLeft, $iTop, $iRightColWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_switchSettingsGui")
		$iTop += $iCtrlHeight + $iSpace
		$mWinLoopData.idButtonExit = GUICtrlCreateButton("🛑", $iLeft, $iTop, $iRightColWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_end")
		_updateListView()
		GUISetState(@SW_SHOW, $hGui)
	EndIf
EndFunc

Func _winListSelectedUp()
	Local $iIndex = _GUICtrlListView_GetSelectionMark($mWinLoopData.hListViewWindows)
	If $iIndex>0 Then
		Local $hwnd = $mWinLoopData.arWindows[$iIndex]
		Local $arWindows = $mWinLoopData.arWindows
		$arWindows[$iIndex] = $mWinLoopData.arWindows[$iIndex-1]
		$arWindows[$iIndex-1] = $hWnd
		$mWinLoopData.arWindows = $arWindows
		_updateListView()
	EndIf
EndFunc

Func _winListSelectedDown()
	Local $iIndex = _GUICtrlListView_GetSelectionMark($mWinLoopData.hListViewWindows)
	If $iIndex>=0 And $iIndex<UBound($mWinLoopData.arWindows)-1 Then
		Local $hwnd = $mWinLoopData.arWindows[$iIndex]
		Local $arWindows = $mWinLoopData.arWindows
		$arWindows[$iIndex] = $mWinLoopData.arWindows[$iIndex+1]
		$arWindows[$iIndex+1] = $hWnd
		$mWinLoopData.arWindows = $arWindows
		_updateListView()
	EndIf
EndFunc

Func _winListRemoveSelected()
	Local $iIndex = _GUICtrlListView_GetSelectionMark($mWinLoopData.hListViewWindows)
	If $iIndex>=0 And $iIndex<UBound($mWinLoopData.arWindows) Then _winListRemove($mWinLoopData.arWindows[$iIndex])
EndFunc

Func _updateListView()
	If Not MapExists($mWinLoopData, "hListViewWindows") Then Return
	_GUICtrlListView_BeginUpdate($mWinLoopData.hListViewWindows)
	_GUICtrlListView_DeleteAllItems($mWinLoopData.hListViewWindows)
	For $i=0 To UBound($mWinLoopData.arWindows)-1
		Local $mWinData = $mWinLoopData.mWindows[$mWinLoopData.arWindows[$i]]
		Local $idItem = GUICtrlCreateListViewItem($mWinData.sPName, $mWinLoopData.idListViewWindows)
		Local $iIndex = _GUICtrlListView_GetItemCount($mWinLoopData.hListViewWindows)-1
		_GUICtrlListView_SetItemParam($mWinLoopData.hListViewWindows, $iIndex, $idItem)
		_GUICtrlListView_SetItemText($mWinLoopData.hListViewWindows, $iIndex, $mWinData.sTitle, 1)
	Next
	_GUICtrlListView_EndUpdate($mWinLoopData.hListViewWindows)
EndFunc

Func _switchSettingsGui()
	Local Static $hGui = 0
	If $hGui<>0 Then
		GUISetState(@SW_ENABLE, $mWinLoopData.hMainGui)
		GUIDelete($hGui)
		$hGui = 0
	Else
		GUISetState(@SW_DISABLE, $mWinLoopData.hMainGui)
		Local $iWidth = 600, $iHeight = 600, $iSpace = 5, $iTop = $iSpace, $iCtrlHeight = 25
		$hGui = GUICreate("Window switch manager", $iWidth, $iHeight)
		GUISetFont(12)
		GUISetOnEvent(-3, "_switchSettingsGui", $hGui)
		GUICtrlCreateLabel("Loop interval: ", $iSpace, $iTop, 100, $iCtrlHeight)
		$mWinLoopData.idInputInterval = GUICtrlCreateInput($mWinLoopData.iInterval, $iSpace*2 + 100, $iTop, 80, $iCtrlHeight)
		$iTop += $iCtrlHeight + $iSpace
		Local $iLabelWidth = 150, $iLabelLeft = ($iWidth-$iLabelWidth-$iSpace)/2, $iButtonWidth = 30
		GUICtrlCreateLabel("Filter configuration", $iLabelLeft, $iTop, $iLabelWidth, $iCtrlHeight, $SS_CENTER)
		$mWinLoopData.idButtonAdd = GUICtrlCreateButton("🞥", $iLabelLeft - $iButtonWidth - $iSpace, $iTop, $iButtonWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_settingsGuiAddFilter")
		$mWinLoopData.idButtonRemove = GUICtrlCreateButton("🗑", $iLabelLeft + $iLabelWidth + $iSpace, $iTop, $iButtonWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_settingsGuiRemoveFilter")
		$iTop += $iCtrlHeight + $iSpace
		Local $iListViewHeight = $iHeight - $iTop - $iCtrlHeight - $iSpace*2, $iListViewWidth = $iWidth-2*$iSpace
		$mWinLoopData.idListViewFilters = GUICtrlCreateListView("     Process name|Process path|Window title|Visible", $iSpace, $iTop, $iListViewWidth, $iListViewHeight, BitOR($LVS_REPORT, $LVS_SHOWSELALWAYS, $LVS_SINGLESEL), BitOR($LVS_EX_CHECKBOXES, $LVS_EX_FULLROWSELECT, $WS_EX_CLIENTEDGE))
		$mWinLoopData.hListViewFilters = GUICtrlGetHandle($mWinLoopData.idListViewFilters)
		Local $iColWidth = ($iListViewWidth-100)/3
		_GUICtrlListView_SetColumnWidth($mWinLoopData.hListViewFilters, 0, $iColWidth)
		_GUICtrlListView_SetColumnWidth($mWinLoopData.hListViewFilters, 1, $iColWidth)
		_GUICtrlListView_SetColumnWidth($mWinLoopData.hListViewFilters, 2, $iColWidth)
		_GUICtrlListView_SetColumnWidth($mWinLoopData.hListViewFilters, 3, 90)
		__ListViewEditInput_ListViewAdd($hGui, $mWinLoopData.idListViewFilters)
		__ListViewEditInput_RegisterFunction($mWinLoopData.idListViewFilters, "_settingsGuiFilterEdited", "changed")
		$iTop += $iListViewHeight + $iSpace
		Local $iButtonWidth = ($iWidth-$iSpace*4)/3
		For $key In MapKeys($mWinLoopData.mFilters)
			Local $mFilter = $mWinLoopData.mFilters[$key]
			GUICtrlCreateListViewItem("", $mWinLoopData.idListViewFilters)
			Local $iIndex = _GUICtrlListView_GetItemCount($mWinLoopData.idListViewFilters)-1
			_GUICtrlListView_SetItemText($mWinLoopData.hListViewFilters, $iIndex, $mFilter.sPName, 0)
			_GUICtrlListView_SetItemText($mWinLoopData.hListViewFilters, $iIndex, $mFilter.sPPath, 1)
			_GUICtrlListView_SetItemText($mWinLoopData.hListViewFilters, $iIndex, $mFilter.sTitle, 2)
			_GUICtrlListView_SetItemText($mWinLoopData.hListViewFilters, $iIndex, $mFilter.bVisible, 3)
			_GUICtrlListView_SetItemChecked($mWinLoopData.hListViewFilters, $iIndex, Not $mFilter.bDisabled)
		Next
		GUICtrlCreateButton("Apply", $iSpace, $iTop, $iButtonWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_settingsApply")
		GUICtrlCreateButton("Save for restart", $iButtonWidth+$iSpace*2, $iTop, $iButtonWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_settingsSave")
		GUICtrlCreateButton("Cancel", $iButtonWidth*2+$iSpace*3, $iTop, $iButtonWidth, $iCtrlHeight)
		GUICtrlSetOnEvent(-1, "_switchSettingsGui")
		GUISetState(@SW_SHOW, $hGui)
	EndIf
EndFunc

Func _settingsGuiFilterEdited($hListView, $iIndex, $iSubIndex)
	If $iSubIndex=3 Then
		Local $sText = StringLower(_GUICtrlListView_GetItemText($hListView, $iIndex, $iSubIndex))
		If $sText<>"" And $sText<>"true" And $sText<>"false" Then
			_GUICtrlListView_SetItemText($hListView, $iIndex, "", $iSubIndex)
		EndIf
	EndIf
EndFunc

Func _settingsGuiAddFilter()
	GUICtrlCreateListViewItem("", $mWinLoopData.idListViewFilters)
	Local $iIndex = _GUICtrlListView_GetItemCount($mWinLoopData.idListViewFilters)-1
	_GUICtrlListView_SetItemChecked($mWinLoopData.idListViewFilters, $iIndex)
EndFunc

Func _settingsGuiRemoveFilter()
	Local $iIndex = _GUICtrlListView_GetSelectionMark($mWinLoopData.hListViewFilters)
	If $iIndex>=0 Then _GUICtrlListView_DeleteItem($mWinLoopData.hListViewFilters, $iIndex)
EndFunc

Func _settingsApply($bClose = True)
	Local $mFilters[]
	For $i=0 To _GUICtrlListView_GetItemCount($mWinLoopData.hListViewFilters)-1
		Local $mFilter[]
		$mFilter.sPName = _GUICtrlListView_GetItemText($mWinLoopData.hListViewFilters, $i, 0)
		$mFilter.sPNameL = StringLower($mFilter.sPName)
		$mFilter.sPPath = _GUICtrlListView_GetItemText($mWinLoopData.hListViewFilters, $i, 1)
		$mFilter.sPPathL = StringLower($mFilter.sPPathL)
		$mFilter.sTitle = _GUICtrlListView_GetItemText($mWinLoopData.hListViewFilters, $i, 2)
		$mFilter.sTitleL = StringLower($mFilter.sTitle)
		$mFilter.bVisible = _GUICtrlListView_GetItemText($mWinLoopData.hListViewFilters, $i, 2)
		If $mFilter.bVisible="true" Then
			$mFilter.bVisible = True
		ElseIf $mFilter.bVisible="false" Then
			$mFilter.bVisible = False
		Else
			$mFilter.bVisible = ""
		EndIf
		$mFilter.bDisabled = Not _GUICtrlListView_GetItemChecked($mWinLoopData.hListViewFilters, $i)
		MapAppend($mFilters, $mFilter)
	Next
	$mWinLoopData.mFilters = $mFilters
	Local $sInterval = GUICtrlRead($mWinLoopData.idInputInterval)
	If StringRegExp($sInterval, "\d+", 0) Then
		$mWinLoopData.iInterval = Int($sInterval)
		If $mWinLoopData.iInterval=0 Then $mWinLoopData.iInterval = $iDefaultIntervalMS
	EndIf
	If $bClose Then _switchSettingsGui()
EndFunc

Func _settingsSave()
	_settingsApply(False)
	_writeToIni()
	_switchSettingsGui()
EndFunc

Func _winHandle($hwnd, $hBefore = Default)
	; ConsoleWrite("Show window: "&$hwnd&" >> "&IsHWnd($hwnd)&" "&WinGetTitle($hwnd)&@crlf)
	; show current window (do not activate => focus stays at current window)
	WinSetState($hwnd, "", @SW_SHOWNA)
	; disable if window should not be maximized
	If Not BitAND(WinGetState($hwnd), 32)>0 Then WinSetState($hwnd, "", @SW_MAXIMIZE)
	; hide window before this
	If $hBefore<>Default Then WinSetState($hBefore, "", @SW_HIDE)
EndFunc

Func _winMapDisplay(ByRef $mWinData)
	For $key in MapKeys($mWinData)
		_winDisplay($mWinData[$key])
	Next
EndFunc

Func _winDisplay($hWndOrData, $bSep = True)
	If IsHWnd($hWndOrData) Then $hWndOrData = _winGetInfo($hWndOrData)
	If Not IsMap($hWndOrData) Or UBound($hWndOrData)<6 Then Return SetError(1, 1, False)
	If $bSep Then ConsoleWrite("----------------------------------------------------------------"&@crlf)
	ConsoleWrite("Window handle    : "&$hWndOrData.hWnd&@crlf)
	ConsoleWrite("Window title     : "&$hWndOrData.sTitle&@crlf)
	ConsoleWrite("Window is visible: "&$hWndOrData.bVisible&@crlf)
	ConsoleWrite("Process id (PID) : "&$hWndOrData.iPId&@crlf)
	ConsoleWrite("Process name     : "&$hWndOrData.sPName&@crlf)
	ConsoleWrite("Process exe path : "&$hWndOrData.sPPath&@crlf)
	Return True
EndFunc

Func _winListLoad()
	If MapExists($mWinLoopData, "idButtonRefresh") Then GUICtrlSetState($mWinLoopData.idButtonRefresh, $GUI_DISABLE)
	_winListClear()
	If UBound($mWinLoopData.mFilters)>0 Then
		Local $mWinData = _winGetMap()
		Local $mFiltered = _winMapFilter($mWinData, $mWinLoopData.mFilters)
		If @error Then ConsoleWrite("Error filtering windows: "&@error&":"&@extended&@crlf)
		For $key In MapKeys($mFiltered)
			_winListAdd($mFiltered[$key].hWnd, $mFiltered[$key], False)
		Next
		_winListOnChange()
	EndIf
	If MapExists($mWinLoopData, "idButtonRefresh") Then GUICtrlSetState($mWinLoopData.idButtonRefresh, $GUI_ENABLE)
EndFunc

Func _winListDisplay()
	For $i=0 To UBound($mWinLoopData.arWindows)-1
		ConsoleWrite("--------------------------- INDEX "&$i&" ---------------------------"&@crlf)
		_winDisplay($mWinLoopData.mWindows[$mWinLoopData.arWindows[$i]], False)
	Next
EndFunc

Func _winListAddActive()
	If _winListAdd(WinGetHandle("[active]")) Then ConsoleWrite("Added new window: "&HWnd(@extended)&" >> "&WinGetTitle(HWnd(@extended))&@crlf)
EndFunc

Func _winListAdd($hwnd, $mData = Default, $bTriggerListChangeEvent = True)
	If Not IsHWnd($hwnd) Then Return SetError(1, 1, False)
	If $mData<>Default And Not IsMap($mData) Then Return SetError(1, 2, False)
	If $mData = Default Then
		$mData = _winGetInfo($hWnd)
		If @error Then Return SetError(2, @error, False)
	EndIf
	$mWinLoopData["mWindows"][$hWnd] = $mData
	Local $arWindows = $mWinLoopData.arWindows
	ReDim $arWindows[UBound($arWindows)+1]
	$arWindows[UBound($arWindows)-1] = $hWnd
	$mWinLoopData.arWindows = $arWindows
	If $bTriggerListChangeEvent Then _winListOnChange()
	Return SetExtended($hwnd, True)
EndFunc

Func _winListRemove($hwnd)
	If Not IsHWnd($hwnd) Then Return SetError(1, 1, False)
	MapRemove($mWinLoopData.mWindows, $hWnd)
	Local $iIndex = _winListGetIndex($hwnd)
	If @error Then Return SetError(1, 1, False)
	Local $arWindows = $mWinLoopData.arWindows
	For $i=$iIndex To UBound($arWindows)-2
		$arWindows[$i] = $arWindows[$i+1]
	Next
	ReDim $arWindows[UBound($arWindows)-1]
	$mWinLoopData.arWindows = $arWindows
	_winListOnChange()
	Return True
EndFunc

Func _winListGetIndex($hwnd)
	For $i=0 to UBound($mWinLoopData.arWindows)-1 Step 1
		If $mWinLoopData.arWindows[$i] = $hwnd Then Return $i
	Next
	Return SetError(1, 1, -1)
EndFunc

Func _winListClear()
	Local $mWindows[], $arWindows[0]
	$mWinLoopData.mWindows = $mWindows
	$mWinLoopData.arWindows = $arWindows
	_winListOnChange()
EndFunc

Func _winListOnChange()
	_updateListView()
EndFunc

; $iInterval 0 runs through all windows without pause. If an interval is provided, only one window will be called (if the time was up), then the function returns
Func _winListLoop($iInterval = Default, $bHandleBefore = True, $bRec = False)
	Local Static $iLastIndex = 0
	Local Static $hTimer = TimerInit()

	If $iInterval = Default Then $iInterval = $mWinLoopData.iInterval
	If UBound($mWinLoopData.mWindows)=0 Then Return

	If $iInterval=0 Then
		If Not $bRec Then
			$iLastIndex=0
			Do
				_winListLoop($iInterval, $bHandleBefore, True)
			Until $iLastIndex=0
			Return
		EndIf
	Else
		If TimerDiff($hTimer)<$iInterval Then Return
		$hTimer = TimerInit()
	EndIf

	; remove all handles of windows that no longer exist
	Local $hWnd, $hWndBefore = Default
	Do
		If UBound($mWinLoopData.arWindows)=0 Then
			$iLastIndex = 0
			Return
		EndIf
		If Not WinExists($mWinLoopData.mWindows[$mWinLoopData.arWindows[$iLastIndex]].hWnd) Then
			_winListRemove($mWinLoopData.arWindows[$iLastIndex])
			If UBound($mWinLoopData.arWindows)=0 Then Return
			If $iLastIndex>=UBound($mWinLoopData.arWindows) Then $iLastIndex=0
		Else
			$hWnd = $mWinLoopData.arWindows[$iLastIndex]
			If $bHandleBefore And UBound($mWinLoopData.arWindows)>1 Then
				If $iLastIndex=0 Then
					$hWndBefore = $mWinLoopData.arWindows[UBound($mWinLoopData.arWindows)-1]
				Else
					$hWndBefore = $mWinLoopData.arWindows[$iLastIndex-1]
				EndIf
			EndIf
			$iLastIndex+=1
			If $iLastIndex>=UBound($mWinLoopData.mWindows) Then $iLastIndex=0
		EndIf
	Until WinExists($mWinLoopData.arWindows[$iLastIndex])
	Local $iIndex = _winListGetIndex($hwnd)
	If MapExists($mWinLoopData, "hListViewWindows") Then GUICtrlSetBkColor(_GUICtrlListView_GetItemParam($mWinLoopData.hListViewWindows, $iIndex), 0xFFFF00)
	If $hWndBefore=Default Then
		_winHandle($hWnd)
	Else
		Local $iIndexBefore = _winListGetIndex($hWndBefore)
		If MapExists($mWinLoopData, "hListViewWindows") Then GUICtrlSetBkColor(_GUICtrlListView_GetItemParam($mWinLoopData.hListViewWindows, $iIndexBefore), 0xFFFFFF)
		_winHandle($hWnd, $hWndBefore)
	EndIf
EndFunc

; $mWinData as provided by _winDataList
; $arFilter as a 2D-Array with $arFilter[n] = [process name, process path, window title, bool must be visible], if any value is "", it will be ignored
; process name and process path are checked to be equal, the window title is checked to be contained (stringinstr). All checks are case-insensitive
Func _winMapFilter(ByRef $mWindows, ByRef $mFilters)
	If Not IsMap($mWindows) Then Return SetError(1, 1, 0)
	If Not IsMap($mFilters) Then Return SetError(1, 2, 0)
	Local $mMapResult[]
	; make all filter string values lowercase beforehand
	Local $arFilterKeys = MapKeys($mFilters)
	For $winKey In MapKeys($mWindows)
		Local $mWindow = $mWindows[$winKey]
		For $iKey in $arFilterKeys
			Local $mFilter = $mFilters[$iKey]
			If Not $mFilter.bDisabled Then
				If $mFilter.sPNameL<>"" And StringLower($mWindow.sPName) <> $mFilter.sPNameL Then ContinueLoop
				If $mFilter.sPPathL<>"" And StringLower($mWindow.sPPath) <> $mFilter.sPPathL Then ContinueLoop
				If $mFilter.sTitleL<>"" And Not StringInStr(StringLower($mWindow.sTitle), $mFilter.sTitleL) Then ContinueLoop
				If $mFilter.bVisible<>"" And $mWindow.bVisible<>$mFilter.bVisible Then ContinueLoop
				$mMapResult[$mWindow.hWnd] = $mWindow
			EndIf
		Next
	Next
	Return $mMapResult
EndFunc

; Returns a map with WinHandles as keys and 1D-Array as values. Array structure: [window handle, window title, pid, process name, process executable path, window is visible]
Func _winGetMap()
	Local $mCache[]
	Local $arWin = WinList()
	Local $mWinData[]
	For $i=1 To UBound($arWin)-1
		$mWinData[$arWin[$i][1]] = _winGetInfo($arWin[$i][1], True)
	Next
	_winGetInfo(Default) ; reset cache
	Return $mWinData
EndFunc

; $mCache is a caching map used to increase performance, _WinAPI_GetProcessName and _WinAPI_GetProcessFileName are rather slow and a lot of processes have a lot of windows (explorer,...)
Func _winGetInfo($hWnd, $bCache = False)
	Local Static $mCache[]
	; reset cache with $hWnd=Default
	If $hWnd=Default Then
		Local $mNewCache[]
		$mCache = $mNewCache
		Return True
	EndIf
	Local $mWinData[]
	Local $iPid = WinGetProcess($hWnd)
	$mWinData.hWnd = $hWnd
	$mWinData.sTitle = WinGetTitle($hWnd)
	$mWinData.iPId = $iPid
	$mWinData.bVisible = BitAND(WinGetState($hWnd), 2)>0
	Local $arProcessInfo[2]
	If Not $bCache Or ($bCache And Not MapExists($mCache, $iPid)) Then
		$arProcessInfo[0] = _WinAPI_GetProcessName($iPid)
		$arProcessInfo[1] = _WinAPI_GetProcessFileName($iPid)
		If $bCache Then $mCache[$iPid] = $arProcessInfo
	ElseIf $bCache Then
		$arProcessInfo = $mCache[$iPid]
	EndIf
	$mWinData.sPName = $arProcessInfo[0]
	$mWinData.sPPath = $arProcessInfo[1]
	Return $mWinData
EndFunc