;Author: Kanashius@autoitscript.com, Kanashius@autoit.de, https://www.kanashius.com

; General code idea: https://github.com/o5x/DesktopIntegration/blob/master/DesktopIntegration/Source.cpp
; Detailed information, what changed at windows 8: https://www.codeproject.com/Articles/856020/Draw-Behind-Desktop-Icons-in-Windows-plus?fid=1875575&df=90&mpp=25&prof=True&sort=Position&view=Normal&spc=Relaxed&fr=26
; Useful tool for finding window information: spy++ from visual studio code

; This UDF works only with Win8+

#include-once
#include <GDIPlus.au3>
#include <Array.au3>
#include <WinAPISysWin.au3>
#include <WindowsConstants.au3>
Global $__Wallpaper_bRunning = True, $__Wallpaper_bExit = False, $__Wallpaper_bClear = False, $__Wallpaper_idTrayPauseResume = -1, $__Wallpaper_iSleepTimer = 0, $__Wallpaper_iSleepTime = 0

;===============================================================================
;
; Function Name:    __Wallpaper_InitDefaultTray()
; Description:      Create a simple tray menu, replacing the default AutoIt tray menu, allowing to pause/resume, clear and exit
; Parameter(s):     none.
; Requirement(s):   none.
; Return Value(s):  none/
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_InitDefaultTray()
	Opt("TrayOnEventMode", 1)
	Opt("TrayMenuMode", 3)
	Local $__Wallpaper_idTrayClear = TrayCreateItem("Clear")
	TrayItemSetOnEvent($__Wallpaper_idTrayClear, "__Wallpaper_Clear")
	$__Wallpaper_idTrayPauseResume = TrayCreateItem("Pause")
	TrayItemSetOnEvent($__Wallpaper_idTrayPauseResume, "__Wallpaper_PauseResume")
	TrayCreateItem("")
	Local $__Wallpaper_idTrayExit = TrayCreateItem("Exit")
	TrayItemSetOnEvent($__Wallpaper_idTrayExit, "__Wallpaper_Exit")
EndFunc

;===============================================================================
;
; Function Name:    __Wallpaper_IsPause()
; Description:      Check if the drawing loop is paused.
; Parameter(s):     none.
; Requirement(s):   none.
; Return Value(s):  True if the drawing loop is paused.
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_IsPause()
	return Not $__Wallpaper_bRunning
EndFunc

;===============================================================================
;
; Function Name:    __Wallpaper_PauseResume()
; Description:      Pauses or Resume the drawing loop.
; Parameter(s):     none.
; Requirement(s):   none.
; Return Value(s):  none.
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_PauseResume()
	$__Wallpaper_bRunning = Not $__Wallpaper_bRunning
	If $__Wallpaper_idTrayPauseResume>0 and $__Wallpaper_bRunning Then TrayItemSetText($__Wallpaper_idTrayPauseResume, "Pause")
	If $__Wallpaper_idTrayPauseResume>0 and Not $__Wallpaper_bRunning Then TrayItemSetText($__Wallpaper_idTrayPauseResume, "Resume")
EndFunc

;===============================================================================
;
; Function Name:    __Wallpaper_Clear()
; Description:      Remove all drawings and clear the background to the default one. Pauses the drawing loop.
; Parameter(s):     none.
; Requirement(s):   none.
; Return Value(s):  none.
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_Clear()
	$__Wallpaper_bRunning = False
	$__Wallpaper_bClear = True
	If $__Wallpaper_idTrayPauseResume>0 Then TrayItemSetText($__Wallpaper_idTrayPauseResume, "Resume")
EndFunc

;===============================================================================
;
; Function Name:    __Wallpaper_Exit()
; Description:      Stop the drawing loop and exit the program
; Parameter(s):     none.
; Requirement(s):   none.
; Return Value(s):  none.
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_Exit()
	$__Wallpaper_bExit = True
EndFunc


;===============================================================================
;
; Function Name:    __Wallpaper_Start()
; Description:      Register Callbacks for the functions and start the loop
; Parameter(s):     $fCallbackDraw		: Callback that is called to draw at the wallpaper-image.
;						Parameter(s):	$hGraphics = GDI+ graphics context to draw at
;										$iWidth = The width of the wallpaper
;										$iHeight = The height of the wallpaper
;										$arScreenInfo = A 2D-Array with the position and size of all screens at the wallpaper-image. The primary screen is always the first one.
;												[0] - $iScreenPosX
;												[1] - $iScreenPosY
;												[2] - $iScreenWidth
;												[3] - $iScreenHeight
;												[4] - $iScreenIsPrimary
;						Return Value(s):  A 2D-Array of Areas to redraw.
;												[0][0] - $iScreenPosX
;												[0][1] - $iScreenPosY
;												[0][2] - $iScreenWidth
;												[0][3] - $iScreenHeight
;										  The SetExtended (@extended) can be used to add a sleep time in between draw calls.
;												...
;					$fCallbackInit		: Callback that is called after the startup to allow initialization.
;						Parameter(s):	$iWidth = The width of the wallpaper
;										$iHeight = The height of the wallpaper
;										$arScreenInfo = A 2D-Array with the position and size of all screens at the wallpaper-image. The primary screen is always the first one.
;												[0] - $iScreenPosX
;												[1] - $iScreenPosY
;												[2] - $iScreenWidth
;												[3] - $iScreenHeight
;												[4] - $iScreenIsPrimary
;					$fCallbackShutdown	: Callback that is called before everything is shutdown to allow cleanup at the user side.
;					$fCallbackProcess	: Callback that is independently from the $fCallbackDraw and is not affected by pausing. Can be used to process own events like GuiGetMsg.
; Requirement(s):   none.
; Return Value(s):  none.
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_Start($fCallbackDraw, $fCallbackInit = Default, $fCallbackShutdown = Default, $fCallbackProcess = Default)
	$arObjects = __Wallpaper_Startup()
	If IsFunc($fCallbackInit) Then $fCallbackInit($arObjects[0], $arObjects[1], $arObjects[2])
	Local $arAreas[0], $iWaitTime
	Do
		If IsFunc($fCallbackProcess) Then $fCallbackProcess()
		If TimerDiff($__Wallpaper_iSleepTimer) > $__Wallpaper_iSleepTime Then
			$__Wallpaper_iSleepTimer = TimerInit()
			If $__Wallpaper_bRunning Then
				For $i=0 To UBound($arAreas)-1 Step 1
					_WinAPI_BitBlt($arObjects[7], $arAreas[$i][0], $arAreas[$i][1], $arAreas[$i][2], $arAreas[$i][3], $arObjects[5], $arAreas[$i][0], $arAreas[$i][1], $SRCCOPY)
				Next
				$arAreas = $fCallbackDraw($arObjects[9], $arObjects[0], $arObjects[1], $arObjects[2])
				$__Wallpaper_iSleepTime = @extended
				For $i=0 To UBound($arAreas)-1 Step 1
					_WinAPI_BitBlt($arObjects[4], $arAreas[$i][0], $arAreas[$i][1], $arAreas[$i][2], $arAreas[$i][3], $arObjects[7], $arAreas[$i][0], $arAreas[$i][1], $SRCCOPY)
				Next
			ElseIf $__Wallpaper_bClear Then
				$__Wallpaper_bClear = False
				__Wallpaper_ClearInternal($arObjects)
			EndIf
		EndIf
		If $__Wallpaper_iSleepTime>10 Then
			Sleep(10)
		EndIf
	Until $__Wallpaper_bExit
	If IsFunc($fCallbackShutdown) Then $fCallbackShutdown()
	__Wallpaper_Shutdown($arObjects)
EndFunc

;===============================================================================
;
; Function Name:    __Wallpaper_GetWallpaperFile()
; Description:      Get the current file used as wallpaper.
; Parameter(s):     none.
; Requirement(s):   none.
; Return Value(s):  The path to the wallpaper file
; Error(s):			1 - No path could be found, return value is ""
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_GetWallpaperFile()
	Local $SPI_GETDESKWALLPAPER = 0x0073
	$tWallpaper = DllStructCreate("char[261]")
	DllStructSetData($tWallpaper, 1, 0)
	DllCall("user32.dll", "int", "SystemParametersInfo", "int", $SPI_GETDESKWALLPAPER, "int", 260, "ptr", DllStructGetPtr($tWallpaper), "int", 0)
	$sPath = DllStructGetData($tWallpaper, 1)
	If $sPath <> "" and FileExists($sPath) Then return $sPath
	$sPath = RegRead("HKEY_CURRENT_USER\Control Panel\Desktop", "WallPaper")
	If $sPath <> "" and FileExists($sPath) Then return $sPath
	$sPath = @AppDataDir&"\Microsoft\Windows\Themes\TranscodedWallpaper"
	If $sPath <> "" and FileExists($sPath) Then return $sPath
	return SetError(1, 0, "")
EndFunc

;===============================================================================
;
; Function Name:    __Wallpaper_SetWallpaper()
; Description:      Set the current wallpaper from a file
; Parameter(s):     $sFile	: The path to the new wallpaper.
; Requirement(s):   none.
; Return Value(s):  True for success, otherwise False
; Error(s):			1 - The path is empty or the file does not exist
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_SetWallpaper($sFile)
	If $sFile = "" or Not FileExists($sFile) Then SetError(1, 0, 0)
	Local $SPI_SETDESKWALLPAPER = 0x0014
    Local $SPIF_UPDATEINIFILE = 0x0001
    Local $SPIF_SENDCHANGE = 0x0002
	if $sFile <> "" Then
		DllCall("user32.dll", "int", "SystemParametersInfo", "int", $SPI_SETDESKWALLPAPER, "int", 0, "str", $sFile, "int", BitOR(1, 2))
		return True
	EndIf
	return False
EndFunc

;======Internel Use Only======================================================
;
; Function Name:    __Wallpaper_Startup
; Description:      Initializes everything. Will be called by __Wallpaper_Start automatically.
; Parameter(s):     none.
; Requirement(s):   none.
; Return Value(s):  Array with needed Objects.
;						[0] - $iWidth: Width of the wallpaper
;						[1] - $iHeight: Height of the wallpaper
;						[2] - $arMonitorInfo: 2D-Array containing posX, posY, width, height, isPrimaryDisplay of all screens
;						[3] - $hWorkerW: The window to draw at
;						[4] - $hDC: The device context to draw at
;						[5] - $hDC_Backup: The backup device context to restore the wallpaper
;						[6] - $hBitmapBackup: The bitmap for the backup
;						[7] - $hDC_Backbuffer: The buffer device context to draw at
;						[8] - $hBitmapBuffer: The bitmap for the buffer
;						[9] - $hGfxCtxt: The graphics context of the buffer to draw at
; Error(s):			1 - Progman window not found
;					2 - WorkerW window not found
;					3 - WorkerW DC window not found
;					4 - Some GDI+ error
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_Startup()
	_GDIPlus_Startup()

	; Create window between background and desktop icons to draw at
	Local $hProgman = __Wallpaper_FindWindowByClass("Progman")
	If @error Then return SetError(1, @error, 0)
	_WinAPI_SendMessageTimeout($hProgman, 0x052C, 0, 0, 1000, 0)

	; Get the WorkerW window between wallpaper and icons
	Local $hWorkerW = __Wallpaper_GetWorkerW()
	If @error Then return SetError(2, @error, 0)

	Local $hDC = _WinAPI_GetDCEx($hWorkerW, 0, 0x403)
	If not $hDC Then return SetError(3, @error, 0)

	; Get size of background
	Local $ptrHBitmap = _WinAPI_GetCurrentObject($hDC, $OBJ_BITMAP)
	If @error Then return SetError(4, @error, 0)
	Local $tBitmap = DllStructCreate($tagBITMAP)
	_WinAPI_GetObject($ptrHBitmap, DllStructGetSize($tBitmap), DllStructGetPtr($tBitmap))
	If @error Then return SetError(4, @error, 0)
	Local $iWidth = DllStructGetData($tBitmap, "bmWidth")
	Local $iHeight = DllStructGetData($tBitmap, "bmHeight")
	$tBitmap = 0
	_WinAPI_DeleteObject($ptrHBitmap)
	If @error Then return SetError(4, @error, 0)

	Local $arMonitorInfo = __Wallpaper_GetScreenInfo()

	; init backup buffer
	Local $hDC_Backup = _WinAPI_CreateCompatibleDC($hDC)
	If @error Then return SetError(4, @error, 0)
	Local $hBitmapBackup = _WinAPI_CreateCompatibleBitmap($hDC, $iWidth, $iHeight)
	If @error Then return SetError(4, @error, 0)
	_WinAPI_SelectObject($hDC_Backup, $hBitmapBackup)
	If @error Then return SetError(4, @error, 0)

	; init drawing buffer
	Local $hDC_Backbuffer = _WinAPI_CreateCompatibleDC($hDC)
	If @error Then return SetError(4, @error, 0)
	Local $hBitmapBuffer = _WinAPI_CreateCompatibleBitmap($hDC, $iWidth, $iHeight)
	If @error Then return SetError(4, @error, 0)
	_WinAPI_SelectObject($hDC_Backbuffer, $hBitmapBuffer)
	If @error Then return SetError(4, @error, 0)

	; load backup buffer
	_WinAPI_BitBlt($hDC_Backup, 0, 0, $iWidth, $iHeight, $hDC, 0, 0, $SRCCOPY)
	If @error Then return SetError(4, @error, 0)

	; initialize drawing buffer graphics object
	Local $hGfxCtxt = _GDIPlus_GraphicsCreateFromHDC($hDC_Backbuffer)
	If @error Then return SetError(4, @error, 0)
	_GDIPlus_GraphicsSetSmoothingMode($hGfxCtxt, $GDIP_SMOOTHINGMODE_HIGHQUALITY)
	If @error Then return SetError(4, @error, 0)
	_GDIPlus_GraphicsSetPixelOffsetMode($hGfxCtxt, $GDIP_PIXELOFFSETMODE_HIGHQUALITY)
	If @error Then return SetError(4, @error, 0)

	$__Wallpaper_iSleepTimer = TimerInit()
	Local $arObjects = [$iWidth, $iHeight, $arMonitorInfo, $hWorkerW, $hDC, $hDC_Backup, $hBitmapBackup, $hDC_Backbuffer, $hBitmapBuffer, $hGfxCtxt]
	return $arObjects
EndFunc

;======Internel Use Only======================================================
;
; Function Name:    __Wallpaper_ClearInternal
; Description:      Clear the background and restore the original wallpaper
; Parameter(s):     $arObjects: Array with needed Objects, created by __Wallpaper_Startup.
; Requirement(s):   none.
; Return Value(s):  none.
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_ClearInternal(Byref $arObjects)
	_WinAPI_BitBlt($arObjects[4], 0, 0, $arObjects[0], $arObjects[1], $arObjects[5], 0, 0, $SRCCOPY)
	__Wallpaper_SetWallpaper(__Wallpaper_GetWallpaperFile())
EndFunc

;======Internel Use Only======================================================
;
; Function Name:    __Wallpaper_Shutdown
; Description:      Clear the wallpaper and shutdown of the UDF, required when the __Wallpaper_Start or __Wallpaper_Startup was called. Exits the program. Will be called by __Wallpaper_Start, when the loop is done.
; Parameter(s):     $arObjects: Array with needed Objects, created by __Wallpaper_Startup.
; Requirement(s):   none.
; Return Value(s):  none.
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_Shutdown(Byref $arObjects)
	__Wallpaper_ClearInternal($arObjects)
	_GDIPlus_GraphicsDispose($arObjects[9])
	_WinAPI_DeleteObject($arObjects[8])
	_WinAPI_DeleteObject($arObjects[7])
	_WinAPI_DeleteObject($arObjects[6])
	_WinAPI_DeleteObject($arObjects[5])
	_WinAPI_ReleaseDC($arObjects[3], $arObjects[4])
	_GDIPlus_Shutdown()
	_WinAPI_RedrawWindow($arObjects[3],"","",BitOR($RDW_ERASE,$RDW_INVALIDATE,$RDW_UPDATENOW,$RDW_FRAME,$RDW_ALLCHILDREN))
	Exit
EndFunc

;======Internel Use Only======================================================
;
; Function Name:    __Wallpaper_GetScreenInfo
; Description:      Get the information of all screens
; Parameter(s):     none.
; Requirement(s):   none.
; Return Value(s):  2D-Array with Monitor Information.
;						[0][0] - X-Screen position
;						[0][1] - Y-Screen position
;						[0][2] - Screen width
;						[0][3] - Screen height
;						[0][4] - True if the screen is the primary screen
;						...
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_GetScreenInfo()
	Local $arMonitors = _WinAPI_EnumDisplayMonitors()
	Local $arMonitorResult[UBound($arMonitors)][5], $iCount = 0
	For $i = 1 To UBound($arMonitors)-1 Step 1
		$arMonitorInfo=_WinAPI_GetMonitorInfo($arMonitors[$i][0])
        If Not @error Then
			$arData = _WinAPI_EnumDisplaySettings($arMonitorInfo[3], $ENUM_CURRENT_SETTINGS)
			If Not @error Then
				$arMonitorResult[$i-1][0] = DllStructGetData($arMonitorInfo[0], 1)
				$arMonitorResult[$i-1][1] = DllStructGetData($arMonitorInfo[0], 2)
				$arMonitorResult[$i-1][2] = $arMonitorResult[$i][0] + $arData[0]
				$arMonitorResult[$i-1][3] = $arMonitorResult[$i][1] + $arData[1]
				$arMonitorResult[$i-1][4] = $arMonitorInfo[2] <> 0
				$iCount+=1
			EndIf
        EndIf
	Next
	ReDim $arMonitorResult[$iCount][5]
	_ArraySort($arMonitorResult, 1, 0, 0, 4)
	If UBound($arMonitorResult)>0 Then
		Local $iYMin=$arMonitorResult[0][0], $iXMin=$arMonitorResult[0][1]
		For $i = 0 To UBound($arMonitorResult)-1 Step 1
			If $arMonitorResult[$i][0]<$iYMin Then $iYMin=$arMonitorResult[$i][0]
			If $arMonitorResult[$i][1]<$iXMin Then $iXMin=$arMonitorResult[$i][1]
		Next
		$iYMin = $iYMin*-1
		$iXMin = $iXMin*-1
		For $i = 0 To UBound($arMonitorResult)-1 Step 1
			$arMonitorResult[$i][0] += $iYMin
			$arMonitorResult[$i][1] += $iXMin
		Next
	EndIf
	return $arMonitorResult
EndFunc


;======Internel Use Only======================================================
;
; Function Name:    __Wallpaper_GetWorkerW
; Description:      Get the window to draw at.
; Parameter(s):     none.
; Requirement(s):   none.
; Return Value(s):  HWND of the worker window
; Error(s):			1 - WorkerW window not found
; 					2 - Error calling __Wallpaper_FindWindowByClass
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_GetWorkerW()
	Local $arWorkerWhwnd = __Wallpaper_FindWindowByClass("WorkerW", Default, True)
	if @error then return SetError(2, 0, 0)
	Local $iPos = -1
	For $i = 0 To UBound($arWorkerWhwnd)-1 Step 1
		If __Wallpaper_FindWindowByClass("SHELLDLL_DefView", $arWorkerWhwnd[$i], False, True) Then
			$iPos = $i+1
			ExitLoop
		EndIf
	Next
	If $iPos<UBound($arWorkerWhwnd) Then return $arWorkerWhwnd[$iPos]
	return SetError(1, 0, 0)
EndFunc

;======Internel Use Only======================================================
;
; Function Name:    __Wallpaper_FindWindowByClass
; Description:      Find the window(s) with the given class
; Parameter(s):     $sClass			: The class string to search for
;					$hWnd			: Default - all windows; All childs, if a handle is provided
;					$bReturnAll		: Default - False; If True, an array with all matching windows is returned
;					$bIncludeChilds	: Default - False; If True, all childs will be searched recursive
; Requirement(s):   none.
; Return Value(s):  HWND of the matching window, or an array with all matching windows
; Error(s):			1 - Window with class not found
; Author(s):        Kanashius
;
;===============================================================================
Func __Wallpaper_FindWindowByClass($sClass, $hWnd = Default, $bReturnAll = False, $bIncludeChilds = False)
	Local $arWins
	If $hWnd = Default Then
		$arWins = _WinAPI_EnumWindows(False)
	Else
		$arWins = _WinAPI_EnumChildWindows($hWnd, False)
	EndIf
	Local $arRes[0]
	Local $i
	For $i = 1 To UBound($arWins)-1 step 1
		If $arWins[$i][1] = $sClass Then
			If $bReturnAll Then
				ReDim $arRes[UBound($arRes)+1]
				$arRes[UBound($arRes)-1] = $arWins[$i][0]
			Else
				return $arWins[$i][0]
			EndIf
		Else
			If $bIncludeChilds Then
				$res = __Wallpaper_FindWindowByClass($sClass, $arWins[$i][0], $bReturnAll, $bIncludeChilds)
				If $bReturnAll Then
					Local $j
					For $j = 0 To UBound($res)-1 Step 1
						ReDim $arRes[UBound($arRes)+1]
						$arRes[UBound($arRes)-1] = $res[$j]
					Next
				Else
					If $res Then return $res
				EndIf
			EndIf
		EndIf
	Next
	If $bReturnAll Then return $arRes
	return SetError(1, 0, 0)
EndFunc