Show-WT.ps1
function Show-WT { <# .Synopsis Shows Images in the Windows Terminal .Description Shows Images in the Windows Terminal. By default, .GIF files will play once, and non-GIFs will stay for 15 seconds. .Example Show-WT -ImagePath .\My.gif # Shows My.gif in the current Windows Terminal profile. .Example Show-WT -ImagePath .\My.gif -Wait -1 # Shows My.gif forever .Link Get-WTProfile .Link Set-WTProfile #> [OutputType([Management.Automation.Job], [Nullable])] param( # The path to an image file. [Parameter(ValueFromPipelineByPropertyName,Position=0)] [ValidatePattern('\.(gif|jpg|jpeg|png)$')] [Alias('FullName','Image','BackgroundImage')] [string] $ImagePath, # If set, will display content in a given profile. # If not set, will attempt to auto-detect the profile. # If the profile cannot be automatically detected, content will be displayed using the default profile settings. # (this will not override an existing image) [string] $ProfileName, # Sets the alignment of the Image to draw over the window background. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('bottom','bottomLeft','bottomRight','center','left','right','top','topLeft','topRight')] [Alias('BackgroundImageAlignment', 'ImageAlignment')] [string] $Alignment= 'center', # Sets the opacity of the Image to draw over the window background. [Parameter(ParameterSetName='ImageFile',ValueFromPipelineByPropertyName)] [Alias('BackgroundImageOpacity','ImageOpacity')] [float] $Opacity = .9, # Sets how the background image is resized to fill the window. [Parameter(ValueFromPipelineByPropertyName)] [ValidateSet('fill','none','uniform','uniformToFill')] [Alias('BackgroundImageStretchMode', 'ImageStretchMode')] [string] $StretchMode= 'uniformToFill', # How long to wait before making the change. # By default, the change will be as quick as possible. [Parameter(ValueFromPipelineByPropertyName)] [timespan] $Wait = 0, # Sets how long the image should be displayed. # If the duration is negative, the image will not be automatically cleared. [Parameter(ValueFromPipelineByPropertyName)] [Timespan] $Duration, # Sets the number of times an animated .gif should be looped. [Parameter(ValueFromPipelineByPropertyName)] [int] $LoopCount = 1, <# When useAcrylic is set to true, it sets the transparency of the window for the profile. Accepts floating point values from 0-1 (default 0.5). #> [Parameter(ValueFromPipelineByPropertyName)] [ValidateRange(0,1)] [float] $AcrylicOpacity, <# When set to true, the window will have an acrylic background. When set to false, the window will have a plain, untextured background. #> [Parameter(ValueFromPipelineByPropertyName)] [switch] $UseAcrylic, <# If provided, will use a pixel shader. #> [Parameter(ValueFromPipelineByPropertyName)] [string] $PixelShader, # If set, will run in a background job. [switch] $AsJob ) begin { #region Prepare Background Job if ($AsJob) { $jobCmd = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Start-ThreadJob','Alias,Cmdlet,Function') if (-not $jobCmd) { $jobCmd = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Start-Job','Alias,Cmdlet,Function') } $jobDef = [ScriptBlock]::Create(@" param([Collections.IDictionary]`$parameters) Import-Module '$($MyInvocation.MyCommand.Module.Path | Split-Path)' $($MyInvocation.MyCommand.Name) @parameters "@) } #endregion Prepare Background Job $getGifLength = { param([string]$resolvedPath) if (-not ('Drawing.Image' -as [type])) { Add-Type -AssemblyName System.Drawing } $img = [Drawing.Image]::FromFile($resolvedPath) $frameCount = try { $img.GetFrameCount([Drawing.Imaging.FrameDimension]::Time) } catch {0} $frameTimes = try { $img.GetPropertyItem(20736).Value } catch { 0 } if ($frameTimes) { $totalMS = 0 for ($i=0; $i -lt $frameCount; $i++) { $totalMS+=[BitConverter]::ToInt32($frameTimes,$i * 4) * 10 } [Timespan]::FromMilliseconds($totalMS) } $img.Dispose() } $accumulateArgs = [Collections.Generic.List[Collections.IDictionary]]::new() } process { $accumulateArgs.Add((@{} + $PSBoundParameters)) } end { foreach ($acc in $accumulateArgs) { foreach ($kv in $acc.GetEnumerator()) { $ExecutionContext.SessionState.PSVariable.Set($kv.Key, $kv.Value) } if (-not $targetProfile) { $targetProfile = if (-not $ProfileName) { Get-WTProfile -Current } else { Get-WTProfile -ProfileName $ProfileName } } if (-not $targetProfile) { $targetProfile = Get-WTProfile -Default } if (-not $targetProfile) { Write-Error "No target profile - WT_PROFILE_ID '$env:WT_PROFILE_ID'" return } if ($wait.TotalMilliseconds) { Start-Sleep -Milliseconds $wait.TotalMilliseconds } $targetProfileJson = $targetProfile | ConvertTo-Json -Depth 10 $targetProfileBackup = $targetProfileJson | ConvertFrom-Json $myParameters = @{} + $PSBoundParameters if ($ImagePath) { $imageFileUri = $imagePath -as [uri] if ($imageFileUri.Authority) { $imageDest = if ($PSVersionTable.OS -and $PSVersionTable.OS -notlike '*windows*') { Join-Path "/home/$($env:USER)/Pictures" $imageFileUri.Segments[-1] } else { Join-Path "$home\Pictures" $imageFileUri.Segments[-1] } $newFile = New-Item -ItemType File -Path $imageDest -Force [Net.Webclient]::new().DownloadFile($imageFileUri, $newFile.FullName) $imagePath = $newFile.FullName } $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($ImagePath) if (-not $resolvedPath) { return } $resolvedPath = Get-Item -LiteralPath $resolvedPath | Select-Object -ExpandProperty Fullname $myParameters['ImagePath'] = "$resolvedPath" } if (-not $ProfileName -and $ENV:WT_PROFILE_ID) { $ProfileName = $myParameters['ProfileName'] = $ENV:WT_PROFILE_ID } if ($AsJob) { $myParameters.Remove('AsJob') & $jobCmd -ScriptBlock $jobDef -ArgumentList $MyParameters return } $wasUsingAcrylic = ($targetProfile.useAcrylic -as [bool]) $oldAcrylicOpacity = ($targetProfile.acrylicOpacity -as [float]) $realPath = if ($resolvedPath -like '/mnt/*') { # If we're trying to show an image with a mounted path $resolvedPath -replace '/mnt/(?<Letter>[a-z])', '${Letter}:\' -replace '/', '\' } elseif ($PSVersionTable.OS -and $PSVersionTable.OS -notlike '*Windows*' -and $env:WSL_DISTRO_NAME) { "\\wsl$\$($env:WSL_DISTRO_NAME)\" + ($resolvedPath -replace '/','\') } else { $resolvedPath } $updatedProfile = $targetProfile if ($ImagePath) { $updatedProfile| Add-Member backgroundImage "$realPath" -Force -PassThru | Add-Member backgroundImageOpacity $Opacity -Force -PassThru | Add-Member backgroundImageAlignment $Alignment -Force -PassThru | Add-Member backgroundImageStrechMode $StretchMode -Force } if ($PixelShader) { $resolvedPixelShaderPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath("$PixelShader") $updatedProfile | Add-Member "experimental.pixelShaderPath" "$resolvedPixelShaderPath" -Force } elseif ($PSBoundParameters.ContainsKey('PixelShader')) { $updatedProfile | Add-Member "experimental.pixelShaderPath" "" -Force } if ($UseAcrylic.IsPresent) { $updatedProfile = $targetProfile | Add-Member useAcrylic ([bool]$UseAcrylic) -Force -PassThru } if ($AcrylicOpacity) { $updatedProfile = $updatedProfile | Add-Member acrylicOpacity $AcrylicOpacity -Force -PassThru } if ($targetProfile.guid) { Set-WTProfile -ProfileName $targetProfile.guid -Confirm:$false -InputObject $updatedProfile } else { Set-WTProfile -Default -Confirm:$false -InputObject $updatedProfile } if (-not $PSBoundParameters['Wait'] -and $ImagePath -like '*.gif') { $Duration = try { & $getGifLength $resolvedPath } catch { [Timespan]::FromSeconds(2.5 * (Get-Item -LiteralPath $resolvedPath).Length /1mb) } } if ($LoopCount -ne 1 -and $ImagePath) { $Duration = [Timespan]::FromMilliseconds((& $getGifLength $resolvedPath).TotalMilliseconds * $LoopCount) } if ($Duration.TotalMilliseconds -ge 0) { Start-Sleep -milliseconds $Duration.TotalMilliseconds $targetProfileBackup | Set-WTProfile -ProfileName $targetProfile.guid -Confirm:$false -Overwrite } } } } |