winfetch-pro.ps1
#!/usr/bin/env -S pwsh -nop #requires -version 5 # (!) This file must to be saved in UTF-8 with BOM encoding in order to work with legacy Powershell 5.x <#PSScriptInfo .VERSION 1.0.6 .GUID 6f7dd5d1-6db3-41e4-8328-ace6c30bf24f .AUTHOR @LunarEclipseCode, Winfetch contributers .PROJECTURI https://github.com/LunarEclipseCode/winfetch-pro .COMPANYNAME .COPYRIGHT .TAGS neofetch screenfetch system-info commandline .LICENSEURI https://github.com/LunarEclipseCode/winfetch-pro/blob/master/LICENSE .ICONURI https://raw.githubusercontent.com/LunarEclipseCode/winfetch-pro/refs/heads/master/images/logo.png .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES #> <# .SYNOPSIS Winfetch-Pro - Neofetch for Windows in PowerShell 5+ .DESCRIPTION winfetch-pro is an enhanced version of winfetch, featuring customizable layouts (double columns, headers, footers), additional system information (WiFi, Bluetooth, cache levels, battery health, BIOS), automatic percent bar alignment, and versatile color customization options. .PARAMETER image Display a pixelated image instead of the usual logo. .PARAMETER ascii Display the image using ASCII characters instead of blocks. .PARAMETER genconf Reset your configuration file to the default. .PARAMETER configpath Specify a path to a custom config file. .PARAMETER noimage Do not display any image or logo; display information only. .PARAMETER logo Sets the version of Windows to derive the logo from. .PARAMETER centerlogo Centers the logo if the longest line in the bottom section exceeds the length of the longest line in the logo. .PARAMETER imgwidth Specify width for image/logo. Default is 35. .PARAMETER alphathreshold Specify minimum alpha value for image pixels to be visible. Default is 50. .PARAMETER blink Make the logo blink. .PARAMETER stripansi Output without any text effects or colors. .PARAMETER all Display all built-in info segments. .PARAMETER help Display this help message. .PARAMETER cpustyle Specify how to show information level for CPU usage .PARAMETER memorystyle Specify how to show information level for RAM usage .PARAMETER diskstyle Specify how to show information level for disks' usage .PARAMETER batterystyle Specify how to show information level for battery .PARAMETER batteryhealthstyle Specify how to show information level for battery health .PARAMETER showdisks Configure which disks are shown, use '-showdisks *' to show all. .PARAMETER showpkgs Configure which package managers are shown, e.g. '-showpkgs winget,scoop,choco'. .PARAMETER colorTopLeft Specify the color of top left quadrant in Windows logo .PARAMETER colorTopRight Specify the color of top right quadrant in Windows logo .PARAMETER colorBottomLeft Specify the color of bottom left quadrant in Windows logo .PARAMETER colorBottomRight Specify the color of bottom right quadrant in Windows logo .PARAMETER allColor Specify the color of all quadrants in Windows logo to override the specific color variables .PARAMETER colorBottomKey Specify the color of keys in bottom section .PARAMETER colorBottomValue Specify the color of values in bottom section .PARAMETER colorLeftKey Specify the color of keys in left column .PARAMETER colorLeftValue Specify the color of values in left column .PARAMETER colorRightKey Specify the color of keys in right column .PARAMETER colorRightValue Specify the color of values in right column .PARAMETER colorGeneralKey Specify the color of keys for elements that start with 'general_' in the left column's config .PARAMETER colorGeneralValue Specify the color of values for elements that start with 'general_' in the left column's config .INPUTS System.String .OUTPUTS System.String[] .NOTES Run Winfetch-Pro without arguments to view core functionality. #> [CmdletBinding()] param( [string][alias('i')]$image, [switch][alias('k')]$ascii, [switch][alias('g')]$genconf, [string][alias('c')]$configpath, [switch][alias('n')]$noimage, [string][alias('l')]$logo, [switch][alias('b')]$blink, [switch][alias('s')]$stripansi, [switch][alias('a')]$all, [switch][alias('h')]$help, [ValidateSet("text", "bar", "textbar", "bartext", "altbar", "textaltbar", "altbartext")][string]$cpustyle = "text", [ValidateSet("text", "bar", "textbar", "bartext", "altbar", "textaltbar", "altbartext")][string]$memorystyle = "textaltbar", [ValidateSet("text", "bar", "textbar", "bartext", "altbar", "textaltbar", "altbartext")][string]$diskstyle = "textaltbar", [ValidateSet("text", "bar", "textbar", "bartext", "altbar", "textaltbar", "altbartext")][string]$batterystyle = "textaltbar", [ValidateSet("text", "bar", "textbar", "bartext", "altbar", "textaltbar", "altbartext")][string]$batteryhealthstyle = "textaltbar", [ValidateScript({ $_ -gt 1 -and $_ -lt $Host.UI.RawUI.WindowSize.Width - 1 })][alias('w')][int]$imgwidth = 35, [ValidateScript({ $_ -ge 0 -and $_ -le 255 })][alias('t')][int]$alphathreshold = 50, [array]$showdisks = @($env:SystemDrive), [array]$showpkgs = @("scoop", "choco"), [string][alias('ctl')]$colorTopLeft = "1;31", [string][alias('ctr')]$colorTopRight = "cyan", [string][alias('cbl')]$colorBottomLeft = "yellow", [string][alias('cbr')]$colorBottomRight = "1;34", [string]$allColor = $null, [string][alias('cBK')]$colorBottomKey = "red", [string][alias('cBV')]$colorBottomValue = "cyan", [string][alias('cLK')]$colorLeftKey = "red", [string][alias('cLV')]$colorLeftValue = "yellow", [string][alias('cRK')]$colorRightKey = "cyan", [string][alias('cRV')]$colorRightValue = "white", [string][alias('cGK')]$colorGeneralKey = "red", [string][alias('cGV')]$colorGeneralValue = "cyan", [bool]$centerlogo = $true ) if (-not ($IsWindows -or $PSVersionTable.PSVersion.Major -eq 5)) { Write-Error "Only supported on Windows." exit 1 } # ===== DISPLAY HELP ===== if ($help) { if (Get-Command -Name less -ErrorAction Ignore) { Get-Help ($MyInvocation.MyCommand.Definition) -Full | less } else { Get-Help ($MyInvocation.MyCommand.Definition) -Full } exit 0 } # ===== CONFIG MANAGEMENT ===== $defaultConfig = @' # ===== WINFETCH-PRO CONFIGURATION ===== # $image = "~/winfetch.png" # $noimage = $true # Display image using ASCII characters # $ascii = $true # Set the version of Windows to derive the logo from. $logo = "Windows 7" # Specify width for image/logo # $imgwidth = 24 # Specify minimum alpha value for image pixels to be visible # $alphathreshold = 50 # Custom ASCII Art # This should be an array of strings, with positive # height and width equal to $imgwidth defined above. # $CustomAscii = @( # "⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣦⠀ ⠀" # "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣶⣶⣾⣷⣶⣆⠸⣿⣿⡟⠀ ⠀" # "⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣷⡈⠻⠿⠟⠻⠿⢿⣷⣤⣤⣄⠀⠀ ⠀" # "⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣦⠀ ⠀" # "⠀⠀⠀⢀⣤⣤⡘⢿⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⣿⡇ ⠀" # "⠀⠀⠀⣿⣿⣿⡇⢸⣿⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⣉⣉⡁ ⠀" # "⠀⠀⠀⠈⠛⠛⢡⣾⣿⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⡇ ⠀" # "⠀⠀⠀⠀⠀⠀⠀⠻⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⠟⠀ ⠀" # "⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⡿⢁⣴⣶⣦⣴⣶⣾⡿⠛⠛⠋⠀⠀ ⠀" # "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠿⠿⢿⡿⠿⠏⢰⣿⣿⣧⠀⠀ ⠀" # "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣿⠟⠀⠀ ⠀" # ) # Make the logo blink # $blink = $true # Display all built-in info segments. # $all = $true # Add a custom info line # function info_custom_time { # return @{ # title = "Time" # content = (Get-Date) # } # } # Configure which disks are shown # $ShowDisks = @("C:", "D:") # Show all available disks # $ShowDisks = @("*") # Configure which package managers are shown # disabling unused ones will improve speed # $ShowPkgs = @("winget", "scoop", "choco") # Use the following option to specify custom package managers. # Create a function with that name as suffix, and which returns # the number of packages. Two examples are shown here: # $CustomPkgs = @("cargo", "just-install") # function info_pkg_cargo { # return (cargo install --list | Where-Object {$_ -like "*:" }).Length # } # function info_pkg_just-install { # return (just-install list).Length # } # Configure how to show info for levels # 'textaltbar' is the default. # 'bar' is for bar only. # 'textbar' is for text + bar. # 'bartext' is for bar + text. # 'textaltbar is for alternate bar only. # 'textaltbar' is for text + alternate bar style. # 'altbartext' is for alternate bar + text. # $cpustyle = 'bar' # $memorystyle = 'textbar' # $diskstyle = 'bartext' # $batterystyle = 'bartext' # Configure color of each quadrant in Windows logo. # Supports colors in format "0;34" (ANSI), "38;5;123" (Extended ANSI) or names. # Supported names # Standard colors: black, red, green, yellow, blue, magenta, cyan, white # Dark colors: Add "dark_" before any standard color (e.g., dark_red, dark_blue) # $colorTopLeft = "1;37" # $colorTopRight = "blue" # $colorBottomLeft = "1;34" # $colorBottomRight = "white" # Configure color of keys and values in the bottom section and in left and right columns # Supports colors in similar way as the previous section on Windows logo # $colorBottomKey = "0;34" # $colorBottomValue = "1;37" # $colorLeftKey = "dark_cyan" # $colorLeftValue = "white" # $colorRightKey = "0;36" # $colorRightValue = "1;37" # Use config_left for a single column layout similar to winfetch # Use both config_left and config_right for a two-column layout # Elements prefixed with "general_" will occupy the entire line, # and their corresponding config_right elements will be ignored. # To set a different color scheme for keys and values in general, use: # $colorGeneralKey = "red" # $colorGeneralValue = "cyan" $config_left = @( "blank" "user" "mini_os" "resolution" "mini_uptime" "cpu_name" "cpu_cores" "ram_total" "ram_form_factor" "blank" "general_memory" "general_disk" "general_battery_health" "blank" "weather_condition" "temp_celcius" "sun" ) $config_right = @( "blank" "hostname" "kernel" "refresh_rate" "cpu_processes" "cpu_clock_speed" "mini_cache" "ram_frequency" "cpu_usage_only" "blank" "blank" "blank" "blank" "blank" "humidity" "feels_like_celcius" "wind" ) # Elements in config_bottom will be placed below the logo. # Note: A non-blank config_bottom without a logo is not supported and may cause formatting issues. $config_bottom = @( "blank" "colorbar_center" ) # Each array in the header will be a separate line, with all elements inside that array being connected by space. # Header elements are placed above the left and right column. # Always keep the @() as the last element of header. Otherwise, if header has one array with 3 elements, # they are printed in 3 different lines instead of being connected in one line. $header = @( @("emoji_date", "emoji_time", "emoji_battery"), @() ) # Specify the color for each element in header (set to white if color not mentioned) $headerColorMap = @{ 'emoji_time' = "yellow" 'emoji_date' = "red" 'emoji_battery' = "cyan" } # Similar to the header, but placed below the left and right columns. $footer = @() # Define colors for each element in the footer, similar to the headerColorMap. $footerColorMap = @{} # Add any of the following elements to config_left, config_right, config_bottom # header, or footer to enable their output # "blank" # "title" # "user" # "hostname" # "dashes" # "os" # "mini-os" # "emoji_date" # Intended to be used in the header # "emoji_time" # Intended to be used in the header # "emoji_battery" # "computer" # "bios" # "kernel" # "motherboard" # "uptime" # Format: 1 day 4 hours 32 minutes # "mini_uptime" # Format: 1d 4h 32min # "display" # "refresh_rate" # "resolution" # Resolution of all monitors # "mini_resolution" # Resolution of the primary monitor # "ps_pkgs" # takes some time # "pkgs" # "pwsh" # "terminal" # "terminal_font" # "theme" # "cpu" # "cpu_usage" # Shows the cpu usage percentage and the number of running processes # "cpu_usage_only" # Shows the cpu usage percentage # "cpu_processes" # Shows the number of running processes # "cpu_name" # "cpu_clock_speed" # "cpu_cores" # "cache" # "mini_cache" # "gpu" # "ram" # "ram_form_factor" # "ram_total" # "ram_frequency" # "memory" # "disk" # "battery" # "battery_health" # "locale" # "region" # "language" # "weather" # "bluetooth" # "wifi" # "local_ip" # "public_ip" # "colorbar" # "colorbar_center" # "gradient" # "gradient_center" # "weather" # "weather_condition" # "humidity" # "sun" # Shows the sunset/sunrise time # "temp_celcius" # "temp_farenheit" # "wind" # "feels_like_celcius" # "feels_like_farenheit" '@ if (-not $configPath) { if ($env:WINFETCH_CONFIG_PATH) { $configPath = $env:WINFETCH_CONFIG_PATH } else { $configPath = "${env:USERPROFILE}\.config\winfetch-pro\config.ps1" } } # generate default config if ($genconf -and (Test-Path $configPath)) { $choiceYes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", ` "overwrite your configuration with the default" $choiceNo = New-Object System.Management.Automation.Host.ChoiceDescription "&No", ` "do nothing and exit" $result = $Host.UI.PromptForChoice("Resetting your config to default will overwrite it.", "Do you want to continue?", ($choiceYes, $choiceNo), 1) if ($result -eq 0) { Remove-Item -Path $configPath } else { exit 1 } } if (-not (Test-Path $configPath) -or [String]::IsNullOrWhiteSpace((Get-Content $configPath))) { New-Item -Type File -Path $configPath -Value $defaultConfig -Force | Out-Null if ($genconf) { Write-Host "Saved default config to '$configPath'." exit 0 } else { Write-Host "Missing config: Saved default config to '$configPath'." } } # Declare essential variables before loading config to avoid errors when the variable is not defined in config $config_right = @() $config_left = @() $config_bottom = @() $header = @() $headerColorMap= @{} $footer = @() $footerColorMap = @{} . $configPath *> $null # prevent config from overriding specified parameters foreach ($param in $PSBoundParameters.Keys) { Set-Variable $param $PSBoundParameters[$param] } if ($all) { $header = @(@("emoji_date", "emoji_time"), @()) $config_left = @( "blank" "title" "dashes" "os" "computer" "bios" "kernel" "motherboard" "uptime" "display" "ps_pkgs" "pkgs" "pwsh" "terminal" "terminal_font" "theme" "cpu" "cache" "gpu" "cpu_usage" "ram" "memory" "disk" "battery" "locale" "weather" "bluetooth" "wifi" "local_ip" "public_ip" ) $config_right = @() $config_bottom = @( "blank" "colorbar_center") } # ===== VARIABLES ===== $e = [char]0x1B $ansiRegex = '([\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d\/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~])))' $cimSession = New-CimSession $os = Get-CimInstance -ClassName Win32_OperatingSystem -Property Caption, OSArchitecture, LastBootUpTime, TotalVisibleMemorySize, FreePhysicalMemory -CimSession $cimSession $t = if ($blink) { "5" } else { "1" } $COLUMNS = $imgwidth $ansiReset = "`e[0m" # ===== UTILITY FUNCTIONS ===== function get_percent_bar { param ( [Parameter(Mandatory)][int]$percent ) if ($percent -gt 100) { $percent = 100 } elseif ($percent -lt 0) { $percent = 0 } $x = [char]9632 $bar = $null if ($isBottomSection -and -not $isRightColumn) { $defaultColor = Get-AnsiCode -colorInput $colorBottomValue } elseif ($isRightColumn) { $defaultColor = Get-AnsiCode -colorInput $colorRightValue } elseif($isGeneralItem) { $defaultColor = Get-AnsiCode -colorInput $colorGeneralValue } else { $defaultColor = Get-AnsiCode -colorInput $colorLeftValue } $bar += "$e[${defaultColor}m[ " for ($i = 1; $i -le ($barValue = ([math]::round($percent / 10))); $i++) { if ($i -le 6) { $bar += "$e[32m$x" } # Green section elseif ($i -le 8) { $bar += "$e[93m$x" } # Yellow section else { $bar += "$e[91m$x" } # Red section } for ($i = 1; $i -le (10 - $barValue); $i++) { $bar += "$e[${defaultColor}m-" } $bar += "$e[${defaultColor}m ]" return $bar } function get_altbar { param ( [Parameter(Mandatory)][int]$percent, [bool]$reverse = $false ) if ($percent -gt 100) { $percent = 100 } elseif ($percent -lt 0) { $percent = 0 } $ScaledPercent = ($percent / 100) * 112 $filledBoxes = [math]::Floor($ScaledPercent / 8) $totalBoxes = 14 $emptyBoxes = $totalBoxes - $filledBoxes $altbar = $null if (-not $reverse) { $filledColor = if ($percent -lt 80) { "39" } else { "203" } # blue for < 80%, red for >= 80% } else { $filledColor = if ($percent -lt 80) { "203" } else { "39" } # red for < 80%, blue for >= 80% when reversed } for ($i = 0; $i -lt $filledBoxes; $i++) { $altbar += "$e[38;5;${filledColor}m$([char]0x2587)" } for ($i = 0; $i -lt $emptyBoxes; $i++) { $altbar += "$e[38;5;250m$([char]0x2587)" } return $altbar } function get_level_info { param ( [string]$barprefix, [string]$style, [int]$percentage, [string]$text, [switch]$altstyle, [bool]$reverse = $false ) if ($stripansi) { if ($style -eq "altbar") { $style = "bar" } if ($style -eq "textaltbar") { $style = "textbar" } if ($style -eq "altbartext") { $style = "bartext" } } switch ($style) { 'bar' { return "$barprefix$(get_percent_bar $percentage)" } 'textbar' { return "$text $(get_percent_bar $percentage)" } 'bartext' { return "$barprefix$(get_percent_bar $percentage) $text" } 'altbar' { return "$barprefix$(get_altbar -percent $percentage -reverse $reverse)" } 'textaltbar' { return "$text $(get_altbar -percent $percentage -reverse $reverse)" } 'altbartext' { return "$barprefix$(get_altbar -percent $percentage -reverse $reverse) $text" } default { if ($altstyle) { return "$percentage% ($text)" } else { return "$text ($percentage%)" } } } } function Get-AnsiCode { param ( [string]$colorInput ) $colorMap = @{ "black" = "1;30" "red" = "1;31" "green" = "1;32" "yellow" = "1;33" "blue" = "1;34" "magenta" = "1;35" "cyan" = "1;36" "white" = "1;37" "dark_black" = "0;30" "dark_red" = "0;31" "dark_green" = "0;32" "dark_yellow" = "0;33" "dark_blue" = "0;34" "dark_magenta" = "0;35" "dark_cyan" = "0;36" "dark_white" = "0;37" } $colorInput = $colorInput.Trim().ToLower() if ($colorInput -match '^\d+(;\d+)*$') { $parts = $colorInput -split ';' foreach ($part in $parts) { if (-not ($part -as [int]) -and $part -ne "0") { throw "Invalid ANSI code part: '$part'. Each part must be a number between 0 and 255." } $num = [int]$part if ($num -lt 0 -or $num -gt 255) { throw "ANSI code part out of range: '$part'. Must be between 0 and 255." } } return $colorInput } elseif ($colorMap.ContainsKey($colorInput)) { return $colorMap[$colorInput] } else { throw "Invalid color input: '$colorInput'." } } function apply_window_colors { param ( [array]$logoArray ) $check = $null -ne $allColor -and $allColor -ne '' $ctl = if ($check) { Get-AnsiCode -colorInput $allColor } else { Get-AnsiCode -colorInput $colorTopLeft } $ctr = if ($check) { Get-AnsiCode -colorInput $allColor } else { Get-AnsiCode -colorInput $colorTopRight } $cbl = if ($check) { Get-AnsiCode -colorInput $allColor } else { Get-AnsiCode -colorInput $colorBottomLeft } $cbr = if ($check) { Get-AnsiCode -colorInput $allColor } else { Get-AnsiCode -colorInput $colorBottomRight } $processedLogo = $logoArray | ForEach-Object { $_ -replace '<CTL>', $ctl ` -replace '<CTR>', $ctr ` -replace '<CBL>', $cbl ` -replace '<CBR>', $cbr } return $processedLogo } function truncate_line { param ( [string]$text, [int]$maxLength ) $length = ($text -replace $ansiRegex, "").Length if ($length -le $maxLength) { return $text } $truncateAmt = $length - $maxLength $truncatedOutput = "" $parts = $text -split $ansiRegex for ($i = $parts.Length - 1; $i -ge 0; $i--) { $part = $parts[$i] if (-not $part.StartsWith([char]27) -and $truncateAmt -gt 0) { $num = if ($truncateAmt -gt $part.Length) { $part.Length } else { $truncateAmt } $truncateAmt -= $num $part = $part.Substring(0, $part.Length - $num) } $truncatedOutput = "$part$truncatedOutput" } return $truncatedOutput } # ===== IMAGE ===== $img = if (-not $noimage) { if ($image) { if ($image -eq 'wallpaper') { $image = (Get-ItemProperty -Path 'HKCU:\Control Panel\Desktop' -Name Wallpaper).Wallpaper } Add-Type -AssemblyName 'System.Drawing' $OldImage = if (Test-Path $image -PathType Leaf) { [Drawing.Bitmap]::FromFile((Resolve-Path $image)) } else { [Drawing.Bitmap]::FromStream((Invoke-WebRequest $image -UseBasicParsing).RawContentStream) } # Divide scaled height by 2.2 to compensate for ASCII characters being taller than they are wide [int]$ROWS = $OldImage.Height / $OldImage.Width * $COLUMNS / $(if ($ascii) { 2.2 } else { 1 }) $Bitmap = New-Object System.Drawing.Bitmap @($OldImage, [Drawing.Size]"$COLUMNS,$ROWS") if ($ascii) { $chars = ' .,:;+iIH$@' for ($i = 0; $i -lt $Bitmap.Height; $i++) { $currline = "" for ($j = 0; $j -lt $Bitmap.Width; $j++) { $p = $Bitmap.GetPixel($j, $i) $currline += "$e[38;2;$($p.R);$($p.G);$($p.B)m$($chars[[math]::Floor($p.GetBrightness() * $chars.Length)])$e[0m" } $currline } } else { for ($i = 0; $i -lt $Bitmap.Height; $i += 2) { $currline = "" for ($j = 0; $j -lt $Bitmap.Width; $j++) { $pixel1 = $Bitmap.GetPixel($j, $i) $char = [char]0x2580 if ($i -ge $Bitmap.Height - 1) { if ($pixel1.A -lt $alphathreshold) { $char = [char]0x2800 $ansi = "$e[49m" } else { $ansi = "$e[38;2;$($pixel1.R);$($pixel1.G);$($pixel1.B)m" } } else { $pixel2 = $Bitmap.GetPixel($j, $i + 1) if ($pixel1.A -lt $alphathreshold -or $pixel2.A -lt $alphathreshold) { if ($pixel1.A -lt $alphathreshold -and $pixel2.A -lt $alphathreshold) { $char = [char]0x2800 $ansi = "$e[49m" } elseif ($pixel1.A -lt $alphathreshold) { $char = [char]0x2584 $ansi = "$e[49;38;2;$($pixel2.R);$($pixel2.G);$($pixel2.B)m" } else { $ansi = "$e[49;38;2;$($pixel1.R);$($pixel1.G);$($pixel1.B)m" } } else { $ansi = "$e[38;2;$($pixel1.R);$($pixel1.G);$($pixel1.B);48;2;$($pixel2.R);$($pixel2.G);$($pixel2.B)m" } } $currline += "$ansi$char$e[0m" } $currline } } $Bitmap.Dispose() $OldImage.Dispose() } elseif (($CustomAscii -is [Array]) -and ($CustomAscii.Length -gt 0)) { $CustomAscii } else { if (-not $logo) { if ($os -Like "*Windows 11 *") { $logo = "Windows 11" } elseif ($os -Like "*Windows 10 *" -Or $os -Like "*Windows 8.1 *" -Or $os -Like "*Windows 8 *") { $logo = "Windows 10" } else { $logo = "Windows 7" } } if ($logo -eq "Windows 11") { $COLUMNS = 32 $logoArray = @( "${e}[${t};<CTL>mlllllllllllllll ${e}[<CTR>mlllllllllllllll", "${e}[${t};<CTL>mlllllllllllllll ${e}[<CTR>mlllllllllllllll", "${e}[${t};<CTL>mlllllllllllllll ${e}[<CTR>mlllllllllllllll", "${e}[${t};<CTL>mlllllllllllllll ${e}[<CTR>mlllllllllllllll", "${e}[${t};<CTL>mlllllllllllllll ${e}[<CTR>mlllllllllllllll", "${e}[${t};<CTL>mlllllllllllllll ${e}[<CTR>mlllllllllllllll", "${e}[${t};<CTL>mlllllllllllllll ${e}[<CTR>mlllllllllllllll", "${e}[${t};<CTL>m ${e}[<CTR>m ", "${e}[${t};<CBL>mlllllllllllllll ${e}[<CBR>mlllllllllllllll", "${e}[${t};<CBL>mlllllllllllllll ${e}[<CBR>mlllllllllllllll", "${e}[${t};<CBL>mlllllllllllllll ${e}[<CBR>mlllllllllllllll", "${e}[${t};<CBL>mlllllllllllllll ${e}[<CBR>mlllllllllllllll", "${e}[${t};<CBL>mlllllllllllllll ${e}[<CBR>mlllllllllllllll", "${e}[${t};<CBL>mlllllllllllllll ${e}[<CBR>mlllllllllllllll", "${e}[${t};<CBL>mlllllllllllllll ${e}[<CBR>mlllllllllllllll" ) @(apply_window_colors -logoArray $logoArray) } elseif ($logo -eq "Windows 11 Alternate") { $COLUMNS = 32 $logoArray = @( "${e}[${t};<CTL>m███████████████ ${e}[<CTR>m███████████████", "${e}[${t};<CTL>m███████████████ ${e}[<CTR>m███████████████", "${e}[${t};<CTL>m███████████████ ${e}[<CTR>m███████████████", "${e}[${t};<CTL>m███████████████ ${e}[<CTR>m███████████████", "${e}[${t};<CTL>m███████████████ ${e}[<CTR>m███████████████", "${e}[${t};<CTL>m███████████████ ${e}[<CTR>m███████████████", "${e}[${t};<CTL>m███████████████ ${e}[<CTR>m███████████████", "${e}[${t};<CTL>m ${e}[<CTR>m ", "${e}[${t};<CBL>m███████████████ ${e}[<CBR>m███████████████", "${e}[${t};<CBL>m███████████████ ${e}[<CBR>m███████████████", "${e}[${t};<CBL>m███████████████ ${e}[<CBR>m███████████████", "${e}[${t};<CBL>m███████████████ ${e}[<CBR>m███████████████", "${e}[${t};<CBL>m███████████████ ${e}[<CBR>m███████████████", "${e}[${t};<CBL>m███████████████ ${e}[<CBR>m███████████████", "${e}[${t};<CBL>m███████████████ ${e}[<CBR>m███████████████" ) @(apply_window_colors -logoArray $logoArray) } elseif ($logo -eq "Windows 10" -Or $logo -eq "Windows 8.1" -Or $logo -eq "Windows 8") { $COLUMNS = 34 $logoArray = @( "${e}[${t};<CTL>m ${e}[<CTR>m....iilll", "${e}[${t};<CTL>m ${e}[<CTR>m....iilllllllllllll", "${e}[${t};<CTL>m ....iillll ${e}[<CTR>mlllllllllllllllllll", "${e}[${t};<CTL>miillllllllllll ${e}[<CTR>mlllllllllllllllllll", "${e}[${t};<CTL>mllllllllllllll ${e}[<CTR>mlllllllllllllllllll", "${e}[${t};<CTL>mllllllllllllll ${e}[<CTR>mlllllllllllllllllll", "${e}[${t};<CTL>mllllllllllllll ${e}[<CTR>mlllllllllllllllllll", "${e}[${t};<CTL>mllllllllllllll ${e}[<CTR>mlllllllllllllllllll", "${e}[${t};<CTL>mllllllllllllll ${e}[<CTR>mlllllllllllllllllll", "${e}[${t};<CTL>m ${e}[<CTR>m ", "${e}[${t};<CBL>mllllllllllllll ${e}[<CBR>mlllllllllllllllllll", "${e}[${t};<CBL>mllllllllllllll ${e}[<CBR>mlllllllllllllllllll", "${e}[${t};<CBL>mllllllllllllll ${e}[<CBR>mlllllllllllllllllll", "${e}[${t};<CBL>mllllllllllllll ${e}[<CBR>mlllllllllllllllllll", "${e}[${t};<CBL>m!!llllllllllll ${e}[<CBR>mlllllllllllllllllll", "${e}[${t};<CBL>m ''''!!llll ${e}[<CBR>mlllllllllllllllllll", "${e}[${t};<CBL>m ${e}[<CBR>m''''!!lllllllllllll", "${e}[${t};<CBL>m ${e}[<CBR>m''''!!lll" ) @(apply_window_colors -logoArray $logoArray) } elseif ($logo -eq "Windows 10 Alternate" -Or $logo -eq "Windows 8.1 Alternate" -Or $logo -eq "Windows 8 Alternate") { $COLUMNS = 34 $logoArray = @( "${e}[${t};<CTL>m ${e}[<CTR>m.ooodMMM", "${e}[${t};<CTL>m ${e}[<CTR>m .oodMMMMMMMMMMMMM", "${e}[${t};<CTL>m ...oodMMMM ${e}[<CTR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CTL>moodMMMMMMMMMMM ${e}[<CTR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CTL>mMMMMMMMMMMMMMM ${e}[<CTR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CTL>mMMMMMMMMMMMMMM ${e}[<CTR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CTL>mMMMMMMMMMMMMMM ${e}[<CTR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CTL>mMMMMMMMMMMMMMM ${e}[<CTR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CTL>mMMMMMMMMMMMMMM ${e}[<CTR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CTL>m ${e}[<CTR>m ", "${e}[${t};<CBL>mMMMMMMMMMMMMMM ${e}[<CBR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CBL>mMMMMMMMMMMMMMM ${e}[<CBR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CBL>mMMMMMMMMMMMMMM ${e}[<CBR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CBL>mMMMMMMMMMMMMMM ${e}[<CBR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CBL>mººPMMMMMMMMMMM ${e}[<CBR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CBL>m '''ººPMMMM ${e}[<CBR>mMMMMMMMMMMMMMMMMMMM", "${e}[${t};<CBL>m ${e}[<CBR>m''ººPMMMMMMMMMMMMM", "${e}[${t};<CBL>m ${e}[<CBR>m'''ºººPMM" ) @(apply_window_colors -logoArray $logoArray) } elseif ($logo -eq "Windows 7" -Or $logo -eq "Windows Vista" -Or $logo -eq "Windows XP") { $COLUMNS = 35 $logoArray = @( "${e}[${t};<CTL>m ,.=:!!t3Z3z., ", "${e}[${t};<CTL>m :tt:::tt333EE3 ", "${e}[${t};<CTL>m Et:::ztt33EEE ${e}[<CTR>m@Ee., ..,", "${e}[${t};<CTL>m ;tt:::tt333EE7 ${e}[<CTR>m;EEEEEEttttt33#", "${e}[${t};<CTL>m :Et:::zt333EEQ. ${e}[<CTR>mSEEEEEttttt33QL", "${e}[${t};<CTL>m it::::tt333EEF ${e}[<CTR>m@EEEEEEttttt33F ", "${e}[${t};<CTL>m ;3=*^``````'*4EEV ${e}[<CTR>m:EEEEEEttttt33@. ", "${e}[${t};<CBL>m ,.=::::it=., ${e}[<CTL>m`` ${e}[<CTR>m@EEEEEEtttz33QF ", "${e}[${t};<CBL>m ;::::::::zt33) ${e}[<CTR>m'4EEEtttji3P* ", "${e}[${t};<CBL>m :t::::::::tt33 ${e}[<CBR>m:Z3z.. ${e}[<CTR>m```` ${e}[<CBR>m,..g. ", "${e}[${t};<CBL>m i::::::::zt33F ${e}[<CBR>mAEEEtttt::::ztF ", "${e}[${t};<CBL>m ;:::::::::t33V ${e}[<CBR>m;EEEttttt::::t3 ", "${e}[${t};<CBL>m E::::::::zt33L ${e}[<CBR>m@EEEtttt::::z3F ", "${e}[${t};<CBL>m{3=*^``````'*4E3) ${e}[<CBR>m;EEEtttt:::::tZ`` ", "${e}[${t};<CBL>m `` ${e}[<CBR>m:EEEEtttt::::z7 ", "${e}[${t};<CBL>m ${e}[<CBR>m'VEzjt:;;z>*`` " ) @(apply_window_colors -logoArray $logoArray) } elseif ($logo -eq "Microsoft") { $COLUMNS = 13 $logoArray = @( "${e}[${t};<CTL>m┌─────┐${e}[<CTR>m┌─────┐", "${e}[${t};<CTL>m│ │${e}[<CTR>m│ │", "${e}[${t};<CTL>m│ │${e}[<CTR>m│ │", "${e}[${t};<CTL>m└─────┘${e}[<CTR>m└─────┘", "${e}[${t};<CBL>m┌─────┐${e}[<CBR>m┌─────┐", "${e}[${t};<CBL>m│ │${e}[<CBR>m│ │", "${e}[${t};<CBL>m│ │${e}[<CBR>m│ │", "${e}[${t};<CBL>m└─────┘${e}[<CBR>m└─────┘" ) @(apply_window_colors -logoArray $logoArray) } elseif ($logo -eq "Windows 2000" -Or $logo -eq "Windows 98" -Or $logo -eq "Windows 95") { $COLUMNS = 45 $logoArray = @( " ${e}[${t};30mdBBBBBBBb" " ${e}[${t};30mdBBBBBBBBBBBBBBBb" " ${e}[${t};30m 000 BBBBBBBBBBBBBBBBBBBB" "${e}[${t};30m::::: 000000 BBBBB${e}[${t};<CTL>mdBB${e}[${t};30mBBBB${e}[${t};<CTR>mBBBb${e}[${t};30mBBBBBBB" "${e}[${t};<CTL>m::::: ${e}[${t};30m====== 000${e}[${t};<CTL>m000 BBBBBBBB${e}[${t};30mBBBB${e}[${t};<CTR>mBBBBBBBBB${e}[${t};30mBBBB" "${e}[${t};<CTL>m::::: ====== ${e}[${t};<CTL>m000000 BBBBBBBB${e}[${t};30mBBBB${e}[${t};<CTR>mBBBBBBBBB${e}[${t};30mBBBB" "${e}[${t};<CTL>m::::: ====== ${e}[${t};<CTL>m000000 BBBBBBBB${e}[${t};30mBBBB${e}[${t};<CTR>mBBBBBBBBB${e}[${t};30mBBBB" "${e}[${t};<CTL>m::::: ====== ${e}[${t};<CTL>m000000 BBBBBBBB${e}[${t};30mBBBB${e}[${t};<CTR>mBBBBBBBBB${e}[${t};30mBBBB" "${e}[${t};<CTL>m::::: ====== 000000 BBBBf${e}[${t};30mBBBBBBBBBBB${e}[${t};<CTR>m`BBBB${e}[${t};30mBBBB" "${e}[${t};30m::::: ${e}[${t};<CTL>m====== 000${e}[${t};30m000 BBBBBBBBBBBBBBBBBBBBBBBBB" "${e}[${t};30m::::: ====== 000000 BBBBB${e}[${t};<CBL>mdBB${e}[${t};30mBBBB${e}[${t};<CBR>mBBBb${e}[${t};30mBBBBB${e}[${t};30mBBBB" "${e}[${t};<CBL>m::::: ${e}[${t};30m====== 000${e}[${t};<CBL>m000 BBBBBBBB${e}[${t};30mBBBB${e}[${t};<CBR>mBBBBBBBBB${e}[${t};30mBBBB" "${e}[${t};<CBL>m::::: ====== 000000 BBBBBBBB${e}[${t};30mBBBB${e}[${t};<CBR>mBBBBBBBBB${e}[${t};30mBBBB" "${e}[${t};<CBL>m::::: ====== 000000 BBBBBBBB${e}[${t};30mBBBB${e}[${t};<CBR>mBBBBBBBBB${e}[${t};30mBBBB" "${e}[${t};<CBL>m::::: ====== 000000 BBBBBBBB${e}[${t};30mBBBB${e}[${t};<CBR>mBBBBBBBBB${e}[${t};30mBBBB" "${e}[${t};<CBL>m::::: ====== 000000 BBBBf${e}[${t};30mBBBBBBBBBBB${e}[${t};<CBR>m`BBBB${e}[${t};30mBBBB" "${e}[${t};30m::::: ${e}[${t};<CBL>m====== 000${e}[${t};30m000 BBBBBf `BBBBBBBBB" "${e}[${t};30m :: ====== 000000 BBf `BBBBB" "${e}[${t};30m == 000000 B BBB" ) @(apply_window_colors -logoArray $logoArray) } else { Write-Error 'The only version logos supported are Windows 11, Windows 10/8.1/8, Windows 7/Vista/XP, Windows 2000/98/95 and Microsoft.' exit 1 } } } # ===== BLANK ===== function info_blank { return @{} } # ===== OS ===== function info_os { return @{ title = "OS" content = "$($os.Caption.TrimStart('Microsoft ')) [$($os.OSArchitecture)]" } } function info_mini_os { return @{ title = "OS" content = "$($os.Caption.TrimStart('Microsoft '))" } } # ===== DATE ===== function info_emoji_date { $CalendarEmoji = [char]::ConvertFromUtf32(0x1F4C5) $FormattedDate = Get-Date -Format " d MMMM yyyy" @{ title = "Date (Emoji)" content = "$CalendarEmoji$FormattedDate" } } # ===== TIME ===== function info_emoji_time { $TempDate = Get-Date $hour = $TempDate.Hour $minute = $TempDate.Minute # Round the minutes to the nearest half-hour # If minutes >= 45, round up the hour and if minutes >= 15 and < 45, set to half-hour # Else, set to the exact hour $roundUp = [int]($minute -ge 45) $halfHour = [int]( ($minute -ge 15) -and ($minute -lt 45) ) * 12 # Convert the hour to 12-hour format $hour = ($hour + $roundUp) % 12 if ($hour -eq 0) { $hour = 12 } # Calculate corresponding clock emoji $emojiCode = 0x1F550 + ($hour - 1) + $halfHour $TimeEmoji = [char]::ConvertFromUtf32($emojiCode) $Time = "$TimeEmoji $($TempDate.ToString('h:mm:ss tt'))" @{ title = "Time (Emoji)" content = $Time } } # ===== BIOS ===== function info_bios { $biosInfo = Get-CimInstance -ClassName Win32_BIOS -Property SMBIOSBIOSVersion, BiosVersion -ErrorAction SilentlyContinue -CimSession $cimSession if (-not $biosInfo) { return @{ title = "BIOS" content = "No BIOS information found" } } $version = $biosInfo.SMBIOSBIOSVersion -join ", " $firmwareVersion = if ($biosInfo.BiosVersion[0]) { $biosInfo.BiosVersion[0] } else { "Firmware N/A" } # Retrieve boot mode from environment variable $bootMode = $env:firmware_type return @{ title = "BIOS ($bootMode)" content = "$version ($firmwareVersion)" } } # ===== WIFI ===== function info_wifi { $wifiInfo = netsh wlan show interfaces $ssid = $signal = $radioType = $authentication = $null # Initialize a count of found fields $fieldsFound = 0 $totalFields = 4 foreach ($line in $wifiInfo) { $trimmedLine = $line.Trim() # Find the first colon to separate key and value $colonIndex = $trimmedLine.IndexOf(':') if ($colonIndex -lt 0) { continue } $key = $trimmedLine.Substring(0, $colonIndex).Trim() $value = $trimmedLine.Substring($colonIndex + 1).Trim() switch ($key) { 'SSID' { if ($value) { $ssid = $value $fieldsFound++ if ($fieldsFound -eq $totalFields) { break } } } 'Signal' { if ($value.EndsWith('%')) { $signal = $value $fieldsFound++ if ($fieldsFound -eq $totalFields) { break } } } 'Radio type' { $radioType = $value switch ($radioType) { '802.11n' { $radioType += ' (v4)' } '802.11ac' { $radioType += ' (v5)' } '802.11ax' { $radioType += ' (v6)' } default { } } $fieldsFound++ if ($fieldsFound -eq $totalFields) { break } } 'Authentication' { switch ($value) { 'WPA2-Personal' { $authentication = 'WPA2' } 'WPA2-Enterprise' { $authentication = 'WPA2-E' } 'WPA3-Personal' { $authentication = 'WPA3' } 'WPA3-Enterprise' { $authentication = 'WPA3-E' } 'Open' { $authentication = 'Open' } default { $authentication = $value } } $fieldsFound++ if ($fieldsFound -eq $totalFields) { break } } default { } } # Exit the loop early if all fields are found if ($fieldsFound -eq $totalFields) { break } } # Check if SSID was found (i.e., connected) if ($ssid) { $wifiInfoString = "$ssid - $radioType - $authentication" if ($signal) { $wifiInfoString += " ($signal)" } return @{ title = 'Wi-Fi' content = $wifiInfoString } } else { return @{ title = 'Wi-Fi' content = 'No Wi-Fi connection found' } } } # ===== MOTHERBOARD ===== function info_motherboard { $motherboard = Get-CimInstance Win32_BaseBoard -CimSession $cimSession -Property Manufacturer, Product return @{ title = "Motherboard" content = "{0} {1}" -f $motherboard.Manufacturer, $motherboard.Product } } # ===== TITLE ===== function info_title { $e = [char]27 # Determine which color key to use for the username and @ sign if (-not $isRightColumn -and -not $isBottomSection) { $colorKey = Get-AnsiCode -colorInput $colorLeftKey $colorValue = Get-AnsiCode -colorInput $colorLeftValue } elseif (-not $isRightColumn -and $isBottomSection) { $colorKey = Get-AnsiCode -colorInput $colorBottomKey $colorValue = Get-AnsiCode -colorInput $colorBottomValue } elseif ($isRightColumn -and -not $isBottomSection) { $colorKey = Get-AnsiCode -colorInput $colorRightKey $colorValue = Get-AnsiCode -colorInput $colorRightValue } $username = [System.Environment]::UserName $computerName = $env:COMPUTERNAME return @{ title = "" content = "$e[${colorKey}m{0}@$e[${colorKey}m{1}$e[0m" -f $username, "$e[${colorValue}m${computerName}$e[0m" } } function info_user { return @{ title = "User" content = [System.Environment]::UserName } } function info_hostname { return @{ title = "Hostname" content = $env:COMPUTERNAME } } # ===== DASHES ===== function info_dashes { if (-not $isRightColumn -and -not $isBottomSection) { $colorValue = Get-AnsiCode -colorInput $colorLeftKey } elseif (-not $isRightColumn -and $isBottomSection) { $colorValue = Get-AnsiCode -colorInput $colorBottomKey } elseif ($isRightColumn -and -not $isBottomSection) { $colorValue = Get-AnsiCode -colorInput $colorRightKey } # Calculate the length based on username and computer name $length = [System.Environment]::UserName.Length + $env:COMPUTERNAME.Length + 1 # Generate the content with the selected color $content = "$([char]27)[${colorValue}m" + ("-" * $length) + "$([char]27)[0m" return @{ title = "" content = $content } } function info_refresh_rate { $refreshRate = Get-CimInstance -ClassName Win32_VideoController -CimSession $cimSession | Select-Object -ExpandProperty CurrentRefreshRate if ($refreshRate) { $refreshRateValue = "$refreshRate Hz" } else { $refreshRateValue = "Unknown" } return @{ title = "Refresh Rate" content = $refreshRateValue } } # ===== COMPUTER ===== function info_computer { $compsys = Get-CimInstance -ClassName Win32_ComputerSystem -Property Manufacturer, Model -CimSession $cimSession return @{ title = "Host" content = '{0} {1}' -f $compsys.Manufacturer, $compsys.Model } } # ===== KERNEL ===== function info_kernel { return @{ title = "Kernel" content = "$([System.Environment]::OSVersion.Version)" } } # ===== UPTIME ===== function info_uptime { @{ title = "Uptime" content = $(switch ([System.DateTime]::Now - $os.LastBootUpTime) { ({ $PSItem.Days -eq 1 }) { '1 day' } ({ $PSItem.Days -gt 1 }) { "$($PSItem.Days) days" } ({ $PSItem.Hours -eq 1 }) { '1 hour' } ({ $PSItem.Hours -gt 1 }) { "$($PSItem.Hours) hours" } ({ $PSItem.Minutes -eq 1 }) { '1 minute' } ({ $PSItem.Minutes -gt 1 }) { "$($PSItem.Minutes) minutes" } }) -join ' ' } } function info_mini_uptime { @{ title = "Uptime" content = $(switch ([System.DateTime]::Now - $os.LastBootUpTime) { ({ $PSItem.Days -eq 1 }) { '1 day' } ({ $PSItem.Days -gt 1 }) { "$($PSItem.Days)d" } ({ $PSItem.Hours -eq 1 }) { '1 hour' } ({ $PSItem.Hours -gt 1 }) { "$($PSItem.Hours)h" } ({ $PSItem.Minutes -eq 1 }) { '1 minute' } ({ $PSItem.Minutes -gt 1 }) { "$($PSItem.Minutes)min" } }) -join ' ' } } # ===== RESOLUTION ===== function info_resolution { Add-Type -AssemblyName System.Windows.Forms $displays = foreach ($monitor in [System.Windows.Forms.Screen]::AllScreens) { "$($monitor.Bounds.Size.Width)x$($monitor.Bounds.Size.Height)" } return @{ title = "Resolution" content = $displays -join ', ' } } # Get the primary screen's resolution function info_mini_resolution { $screen = [System.Windows.Forms.Screen]::PrimaryScreen $resolution = "$($screen.Bounds.Width)x$($screen.Bounds.Height)" return @{ title = "Resolution" content = $resolution } } # ===== TERMINAL ===== # this section works by getting the parent processes of the current powershell instance. function info_terminal { $programs = 'powershell', 'pwsh', 'winpty-agent', 'cmd', 'zsh', 'sh', 'bash', 'fish', 'env', 'nu', 'elvish', 'csh', 'tcsh', 'python', 'xonsh' if ($PSVersionTable.PSEdition.ToString() -ne 'Core') { $parent = Get-Process -Id (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $PID" -Property ParentProcessId -CimSession $cimSession).ParentProcessId -ErrorAction Ignore for () { if ($parent.ProcessName -in $programs) { $parent = Get-Process -Id (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $($parent.ID)" -Property ParentProcessId -CimSession $cimSession).ParentProcessId -ErrorAction Ignore continue } break } } else { $parent = (Get-Process -Id $PID).Parent for () { if ($parent.ProcessName -in $programs) { $parent = (Get-Process -Id $parent.ID).Parent continue } break } } $terminal = switch ($parent.ProcessName) { { $PSItem -in 'explorer', 'conhost' } { 'Windows Console' } 'Console' { 'Console2/Z' } 'ConEmuC64' { 'ConEmu' } 'WindowsTerminal' { 'Windows Terminal' } 'FluentTerminal.SystemTray' { 'Fluent Terminal' } 'Code' { 'Visual Studio Code' } default { $PSItem } } if (-not $terminal) { $terminal = "$e[91m(Unknown)" } return @{ title = "Terminal" content = $terminal } } # ===== TERMINAL FONT ===== function info_terminal_font { $programs = 'powershell', 'pwsh', 'winpty-agent', 'cmd', 'zsh', 'sh', 'bash', 'fish', 'env', 'nu', 'elvish', 'csh', 'tcsh', 'python', 'xonsh' $childProcesses = @{} foreach ($prog in $programs) { $childProcesses[$prog.ToLower()] = $true } function Get-ParentProcessId { param ($processId) try { $proc = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $processId" -Property ParentProcessId -ErrorAction SilentlyContinue return $proc.ParentProcessId } catch { return $null } } # Traverse the process tree to find the terminal process $currentProcess = Get-Process -Id $PID -ErrorAction SilentlyContinue if (-not $currentProcess) { return @{ title = "Terminal Font" content = "Process not found" } } while ($childProcesses.ContainsKey($currentProcess.ProcessName.ToLower())) { $parentPid = Get-ParentProcessId -processId $currentProcess.Id if (-not $parentPid) { break } $currentProcess = Get-Process -Id $parentPid -ErrorAction SilentlyContinue if (-not $currentProcess) { break } } $terminalProcess = $currentProcess $fontInfo = $null $envLocalAppData = $env:LOCALAPPDATA $envAppData = $env:APPDATA switch ($terminalProcess.ProcessName) { 'WindowsTerminal' { $wtSettingsPath = "$envLocalAppData\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json" if (Test-Path $wtSettingsPath) { try { $wtSettings = Get-Content -Path $wtSettingsPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop $defaultProfile = $wtSettings.profiles.list | Where-Object { $_.guid -eq $wtSettings.defaultProfile } | Select-Object -First 1 if ($defaultProfile.font.face) { if ($defaultProfile.font.size) { $fontInfo = "$($defaultProfile.font.face) ($([math]::Round($defaultProfile.font.size, 1))pt)" } else { $fontInfo = "$($defaultProfile.font.face)" } } } catch { # Handle JSON parsing or property access errors silently } } } 'Code' { $vscodeSettingsPath = "$envAppData\Code\User\settings.json" if (Test-Path $vscodeSettingsPath) { try { $vscodeSettings = Get-Content -Path $vscodeSettingsPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop $fontName = $vscodeSettings.'terminal.integrated.fontFamily' $fontSize = $vscodeSettings.'terminal.integrated.fontSize' if ($fontName) { if ($fontSize) { $fontInfo = "$fontName ($([math]::Round($fontSize, 1))pt)" } else { $fontInfo = "$fontName" } } } catch { } } } } if (-not $fontInfo) { $fontInfo = "Unknown" } return @{ title = "Terminal Font" content = $fontInfo } } # ===== DISPLAY ===== function info_display { Add-Type -AssemblyName System.Windows.Forms [System.Collections.ArrayList]$lines = @() foreach ($monitor in [System.Windows.Forms.Screen]::AllScreens) { $width = $monitor.Bounds.Size.Width $height = $monitor.Bounds.Size.Height $refreshRate = Get-CimInstance -ClassName Win32_VideoController -CimSession $cimSession | Select-Object -ExpandProperty CurrentRefreshRate [void]$lines.Add(@{ title = "Display" content = "$($width)x$($height) @ $($refreshRate)Hz" }) } return $lines } # ===== THEME ===== function info_theme { $themeinfo = Get-ItemProperty -Path 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize' -Name SystemUsesLightTheme, AppsUseLightTheme $themename = (Get-ItemProperty -Path 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes' -Name CurrentTheme).CurrentTheme.Split('\')[-1].Replace('.theme', '') $systheme = if ($themeinfo.SystemUsesLightTheme) { "Light" } else { "Dark" } $apptheme = if ($themeinfo.AppsUseLightTheme) { "Light" } else { "Dark" } return @{ title = "Theme" content = "$themename (System: $systheme, Apps: $apptheme)" } } # ===== CPU ===== function info_cpu { $cpu = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Env:COMPUTERNAME).OpenSubKey("HARDWARE\DESCRIPTION\System\CentralProcessor\0") $cpuname = $cpu.GetValue("ProcessorNameString") $cpuname = if ($cpuname.Contains('@')) { ($cpuname -Split '@')[0].Trim() } else { $cpuname.Trim() } return @{ title = "CPU" content = "$cpuname @ $($cpu.GetValue("~MHz") / 1000)GHz" # [math]::Round($cpu.GetValue("~MHz") / 1000, 1) is 2-5ms slower } } function info_cpu_name { $cpuRegistryKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Env:COMPUTERNAME).OpenSubKey("HARDWARE\DESCRIPTION\System\CentralProcessor\0") # Truncate name to fit it in two columns $cpuName = $cpuRegistryKey.GetValue("ProcessorNameString") if ($cpuName.Contains('@')) { $cpuName = ($cpuName -Split '@')[0].Trim() } if ($cpuName.Contains('with')) { $cpuName = ($cpuName -Split 'with')[0].Trim() } $cpuName = $cpuName -replace '\(TM\)', '' $cpuName = $cpuName -replace 'CPU', '' $cpuName = $cpuName.Trim() $cpuName = $cpuName -replace '^\S+\s*', '' return @{ title = "CPU" content = $cpuName } } function info_cpu_clock_speed { $cpu = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Env:COMPUTERNAME).OpenSubKey("HARDWARE\DESCRIPTION\System\CentralProcessor\0") return @{ title = "Clock Speed" content = "$($cpu.GetValue("~MHz") / 1000) GHz" } } function info_cpu_cores { $cpuInfo = Get-CimInstance -ClassName Win32_Processor -Property NumberOfCores, NumberOfLogicalProcessors | Select-Object -First 1 return @{ title = "Cores/Threads" content = "$($cpuInfo.NumberOfCores)/$($cpuInfo.NumberOfLogicalProcessors)" } } # ===== GPU ===== function info_gpu { [System.Collections.ArrayList]$lines = @() foreach ($gpu in Get-CimInstance -ClassName Win32_VideoController -Property Name -CimSession $cimSession) { [void]$lines.Add(@{ title = "GPU" content = $gpu.Name }) } return $lines } # ===== CACHE ===== function Get-FormattedCache { param ( [array]$cacheMemory, [bool]$includeSpaces ) $cacheLevels = "" $cacheSizes = "" for ($i = 0; $i -lt $cacheMemory.Count; $i++) { $sizeInKB = $cacheMemory[$i].MaxCacheSize if ($i -gt 0) { $cacheLevels += "/" } $cacheLevels += "L$($i + 1)" if ($i -gt 0) { $cacheSizes += "/" } if ($sizeInKB -ge 1024) { $sizeInMB = [math]::round($sizeInKB / 1024, 1) $cacheSizes += "$sizeInMB" + ($includeSpaces ? " MB" : "MB") } else { $cacheSizes += "$sizeInKB" + ($includeSpaces ? " KB" : "KB") } } return @{ Levels = $cacheLevels Sizes = $cacheSizes } } function info_cache { $cacheMemory = Get-CimInstance -ClassName Win32_CacheMemory -Property MaxCacheSize | Sort-Object -Property MaxCacheSize $formattedCache = Get-FormattedCache -cacheMemory $cacheMemory -includeSpaces $true return @{ title = "$($formattedCache.Levels) Cache" content = $formattedCache.Sizes } } function info_mini_cache { $cacheMemory = Get-CimInstance -ClassName Win32_CacheMemory -Property MaxCacheSize $formattedCache = Get-FormattedCache -cacheMemory $cacheMemory -includeSpaces $false return @{ title = $formattedCache.Levels content = $formattedCache.Sizes } } # ===== RAM ===== $formFactorMap = @{ 0 = "Unknown" 1 = "Other" 2 = "SIP" 3 = "DIP" 4 = "ZIP" 5 = "SOJ" 7 = "SIMM" 8 = "DIMM" 9 = "TSOP" 10 = "PGA" 11 = "RIMM" 12 = "SODIMM" 13 = "SRIMM" 14 = "SMD" 15 = "SSMP" 16 = "QFP" 17 = "TQFP" 18 = "SOIC" 19 = "LCC" 20 = "PLCC" 21 = "BGA" 22 = "FPBGA" 23 = "LGA" 24 = "FB-DIMM" } function info_ram { [System.Collections.ArrayList]$lines = @() $ramInfo = Get-CimInstance -ClassName Win32_PhysicalMemory -Property Capacity, SMBIOSMemoryType, Speed, FormFactor -CimSession $cimSession # Track the number of RAM modules $ramCount = $ramInfo.Count $ramIndex = 1 foreach ($ram in $ramInfo) { if ($ram.Capacity -lt 1GB) { $capacityMB = [math]::Round($ram.Capacity / 1MB, 2) $capacityStr = "$capacityMB MB" } else { $capacityGB = [math]::Round($ram.Capacity / 1GB, 2) $capacityStr = "$capacityGB GB" } # Map SMBIOSMemoryType to Memory Type switch ($ram.SMBIOSMemoryType) { 0 { $memoryType = "Unknown" } 20 { $memoryType = "DDR" } 21 { $memoryType = "DDR2" } 24 { $memoryType = "DDR3" } 26 { $memoryType = "DDR4" } 30 { $memoryType = "DDR5" } default { $memoryType = "Other" } } $speed = if ($ram.Speed) { "$($ram.Speed) MHz" } else { "Not Available" } $formFactor = $formFactorMap[[int]$ram.FormFactor] ?? "Unknown" $ramString = "$capacityStr $memoryType @ $speed ($formFactor)" if ($ramCount -eq 1) { $ramTitle = "RAM" } else { $ramTitle = "RAM$ramIndex" $ramIndex++ } [void]$lines.Add(@{ title = $ramTitle content = $ramString }) } return $lines } function info_ram_form_factor { $ramInfo = Get-CimInstance -ClassName Win32_PhysicalMemory -Property FormFactor -CimSession $cimSession if ($null -eq $ramInfo) { return @{ title = "Form Factor" content = "N/A" } } $ram = $ramInfo[0] $formFactor = $formFactorMap[[int]$ram.FormFactor] ?? "Unknown" return @{ title = "Form Factor" content = $formFactor } } function info_ram_total { $ramInfo = Get-CimInstance -ClassName Win32_PhysicalMemory -Property Capacity, SMBIOSMemoryType -CimSession $cimSession $totalCapacity = 0 $lowestMemoryType = 99 foreach ($ram in $ramInfo) { $totalCapacity += $ram.Capacity # Determine the lowest DDR version if ($ram.SMBIOSMemoryType -in 20..30) { $lowestMemoryType = [math]::Min($lowestMemoryType, $ram.SMBIOSMemoryType) } } $totalCapacityGB = [math]::Round($totalCapacity / 1GB) switch ($lowestMemoryType) { 20 { $memoryType = "DDR" } 21 { $memoryType = "DDR2" } 24 { $memoryType = "DDR3" } 26 { $memoryType = "DDR4" } 30 { $memoryType = "DDR5" } default { $memoryType = "Unknown DDR" } } return @{ title = "RAM" content = "${totalCapacityGB}GB $memoryType" } } function info_ram_frequency { $ramInfo = Get-CimInstance -ClassName Win32_PhysicalMemory -Property Speed -CimSession $cimSession $lowestFrequency = [int]::MaxValue foreach ($ram in $ramInfo) { if ($ram.Speed) { $lowestFrequency = [math]::Min($lowestFrequency, $ram.Speed) } } if ($lowestFrequency -eq [int]::MaxValue) { $output = "Unknown" } else { $output = "$lowestFrequency MHz" } return @{ title = "Frequency" content = $output } } # ===== BLUETOOTH ===== function info_bluetooth { # Retrieve the Name property of the first Bluetooth device with Status 'OK' $device = Get-CimInstance -ClassName Win32_PnPEntity ` -Filter "PNPClass = 'Bluetooth' AND Status = 'OK'" ` -Property Name ` -ErrorAction SilentlyContinue | Select-Object -First 1 if ($device) { $name = $device.Name # Remove these words from the end to keep content short $suffixes = @(" AvRCP Transport", "(R)") foreach ($suffix in $suffixes) { if ($name.EndsWith($suffix, [System.StringComparison]::InvariantCultureIgnoreCase)) { $name = $name.Substring(0, $name.Length - $suffix.Length) break # Exit loop after first match } } return @{ title = "Bluetooth" content = $name.Trim() } } else { return @{ title = "Bluetooth" content = "No Bluetooth device found" } } } # ===== CPU USAGE ===== function get_cpu_usage { # Get all running processes $processes = [System.Diagnostics.Process]::GetProcesses() $loadPercent = 0 $processCount = $processes.Count $cpuCount = [System.Environment]::ProcessorCount $currentTime = [System.DateTime]::Now foreach ($process in $processes) { try { # Calculate the timespan in seconds since the process started $timeSpanSeconds = ($currentTime - $process.StartTime).TotalSeconds if ($timeSpanSeconds -gt 0) { # Calculate CPU usage for the process $loadPercent += ($process.CPU * 100) / $timeSpanSeconds / $cpuCount } } catch { continue } } return @{ LoadPercent = [math]::Round($loadPercent, 2) ProcessCount = $processCount } } function info_cpu_usage { $data = $(get_cpu_usage) return @{ Title = "CPU Usage" Content = get_level_info "" $cpuStyle $data.LoadPercent "$($data.ProcessCount) processes" -AltStyle } } function info_cpu_usage_only { $data = $(get_cpu_usage).LoadPercent return @{ Title = "CPU Usage" Content = "${data}%" } } function info_cpu_processes { $processes = [System.Diagnostics.Process]::GetProcesses() return @{ title = "Processes" content = $processes.Count } } # ===== MEMORY ===== function info_memory { $total = $os.TotalVisibleMemorySize / 1mb $used = ($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / 1mb $usage = [math]::floor(($used / $total * 100)) return @{ title = "Memory" content = get_level_info "" $memorystyle $usage "$($used.ToString("#.##")) GiB / $($total.ToString("#.##")) GiB" } } # ===== DISK USAGE ===== function info_disk { [System.Collections.ArrayList]$lines = @() function to_units($value) { if ($value -gt 1tb) { return "$([math]::round($value / 1tb, 1)) TiB" } else { return "$([math]::floor($value / 1gb)) GiB" } } [System.IO.DriveInfo]::GetDrives().ForEach{ $diskLetter = $_.Name.SubString(0, 2) if ($showDisks.Contains($diskLetter) -or $showDisks.Contains("*")) { try { if ($_.TotalSize -gt 0) { $used = $_.TotalSize - $_.AvailableFreeSpace $usage = [math]::Floor(($used / $_.TotalSize * 100)) [void]$lines.Add(@{ title = "Disk ($diskLetter)" content = get_level_info "" $diskstyle $usage "$(to_units $used) / $(to_units $_.TotalSize)" }) } } catch { [void]$lines.Add(@{ title = "Disk ($diskLetter)" content = "(failed to get disk usage)" }) } } } return $lines } # ===== POWERSHELL VERSION ===== function info_pwsh { return @{ title = "Shell" content = "PowerShell v$($PSVersionTable.PSVersion)" } } # ===== POWERSHELL PACKAGES ===== function info_ps_pkgs { $ps_pkgs = @() # Get all installed packages $pgp = Get-Package -ProviderName PowerShellGet # Get the number of packages where the tags contains PSModule or PSScript $modulecount = $pgp.Where({ $_.Metadata["tags"] -like "*PSModule*" }).count $scriptcount = $pgp.Where({ $_.Metadata["tags"] -like "*PSScript*" }).count if ($modulecount) { if ($modulecount -eq 1) { $modulestring = "1 Module" } else { $modulestring = "$modulecount Modules" } $ps_pkgs += "$modulestring" } if ($scriptcount) { if ($scriptcount -eq 1) { $scriptstring = "1 Script" } else { $scriptstring = "$scriptcount Scripts" } $ps_pkgs += "$scriptstring" } if (-not $ps_pkgs) { $ps_pkgs = "(none)" } return @{ title = "PS Packages" content = $ps_pkgs -join ', ' } } # ===== PACKAGES ===== function info_pkgs { $pkgs = @() if ("winget" -in $ShowPkgs -and (Get-Command -Name winget -ErrorAction Ignore)) { $wingetpkg = (winget list | Where-Object { $_.Trim("`n`r`t`b-\|/ ").Length -ne 0 } | Measure-Object).Count - 1 if ($wingetpkg) { $pkgs += "$wingetpkg (system)" } } if ("choco" -in $ShowPkgs -and (Get-Command -Name choco -ErrorAction Ignore)) { $chocopkg = Invoke-Expression $( "(& choco list" + $(if ([version](& choco --version).Split('-')[0]` -lt [version]'2.0.0') { " --local-only" }) + ")[-1].Split(' ')[0] - 1") if ($chocopkg) { $pkgs += "$chocopkg (choco)" } } if ("scoop" -in $ShowPkgs) { $scoopdir = if ($Env:SCOOP) { "$Env:SCOOP\apps" } else { "$Env:UserProfile\scoop\apps" } if (Test-Path $scoopdir) { $scooppkg = (Get-ChildItem -Path $scoopdir -Directory).Count - 1 } if ($scooppkg) { $pkgs += "$scooppkg (scoop)" } } foreach ($pkgitem in $CustomPkgs) { if (Test-Path Function:"info_pkg_$pkgitem") { $count = & "info_pkg_$pkgitem" $pkgs += "$count ($pkgitem)" } } if (-not $pkgs) { $pkgs = "(none)" } return @{ title = "Packages" content = $pkgs -join ', ' } } # ===== BATTERY ===== function info_battery { Add-Type -AssemblyName System.Windows.Forms $battery = [System.Windows.Forms.SystemInformation]::PowerStatus # If there's no battery if ($battery.BatteryChargeStatus -eq 'NoSystemBattery') { return @{ title = "Battery" content = get_level_info "" $batterystyle "N/A" "None" -altstyle -reverse $true } } # Get the current battery percentage $batteryLevel = [math]::round($battery.BatteryLifePercent * 100) # Determine battery status if ($battery.BatteryChargeStatus -like '*Charging*') { # Charging state: try to calculate time to full charge $batteryData = Get-CimInstance -ClassName BatteryFullChargedCapacity -Namespace ROOT\WMI -ErrorAction SilentlyContinue if (-not $batteryData) { return @{ title = "Battery" content = get_level_info "" $batterystyle "$batteryLevel" "$batteryLevel% (Charging)" -altstyle -reverse $true } } $fullChargeCapacity = $batteryData.FullChargedCapacity $currentCharge = $battery.BatteryLifePercent * $fullChargeCapacity $batteryStatus = Get-CimInstance -Namespace "ROOT\WMI" -ClassName BatteryStatus -ErrorAction SilentlyContinue $chargingRate = if ($batteryStatus) { $batteryStatus.ChargeRate } else { $null } if (-not $chargingRate) { return @{ title = "Battery" content = get_level_info "" $batterystyle "$batteryLevel" "$batteryLevel% (Charging)" -altstyle -reverse $true } } # If not at full charge, calculate time to full charge if ($batteryLevel -ne 100) { $remainingToCharge = $fullChargeCapacity - $currentCharge $timeToFullCharge = ($remainingToCharge / $chargingRate) * 60 # Convert to minutes $hours = [math]::Floor($timeToFullCharge / 60) $minutes = [math]::Ceiling($timeToFullCharge % 60) if ($hours -gt 0) { $timeToCharge = "${hours}h ${minutes}min to recharge" } else { $timeToCharge = "${minutes}min to recharge" } return @{ title = "Battery" content = get_level_info "" $batterystyle "$batteryLevel" "$batteryLevel% ($timeToCharge)" -altstyle -reverse $true } } return @{ title = "Battery" content = get_level_info "" $batterystyle "$batteryLevel" "$batteryLevel% (Charging)" -altstyle -reverse $true } } elseif ($battery.PowerLineStatus -like '*Online*') { return @{ title = "Battery" content = get_level_info "" $batterystyle "$batteryLevel" "$batteryLevel% (Plugged In)" -altstyle -reverse $true } } else { $timeRemaining = $battery.BatteryLifeRemaining / 60 if ($timeRemaining -ge 0) { $hours = [math]::Floor($timeRemaining / 60) $minutes = [math]::Floor($timeRemaining % 60) if ($hours -gt 0) { $timeFormatted = "${hours}h ${minutes}min left" } else { $timeFormatted = "${minutes}min left" } return @{ title = "Battery" content = get_level_info "" $batterystyle "$batteryLevel" "$batteryLevel% ($timeFormatted)" -altstyle -reverse $true } } return @{ title = "Battery" content = get_level_info "" $batterystyle "$batteryLevel" "$batteryLevel% (Discharging)" -altstyle -reverse $true } } } function info_emoji_battery { Add-Type -AssemblyName System.Windows.Forms $battery = [System.Windows.Forms.SystemInformation]::PowerStatus $batteryEmojiFull = [char]::ConvertFromUtf32(0x1F50B) # 🔋 Battery (30% or more) if ($battery.BatteryChargeStatus -eq 'NoSystemBattery') { return @{ title = "Battery" content = "${batteryEmojiFull} None" } } $chargingEmoji = [char]::ConvertFromUtf32(0x26A1) # ⚡ Recharge $batteryEmojiLow = [char]::ConvertFromUtf32(0x1FAAB) # 🪫 Low battery $batteryLevel = $([math]::round($battery.BatteryLifePercent * 100)) if ($battery.BatteryChargeStatus -like '*Charging*') { $batteryData = Get-CimInstance -ClassName BatteryFullChargedCapacity -Namespace ROOT\WMI -CimSession $cimSession -Property FullChargedCapacity -ErrorAction SilentlyContinue if (-not $batteryData) { return @{ title = "Battery" content = "${chargingEmoji}${batteryLevel} (Charging)" } } $fullChargeCapacity = $batteryData.FullChargedCapacity $currentCharge = $battery.BatteryLifePercent * $fullChargeCapacity $chargingRate = (Get-CimInstance -Namespace "ROOT\WMI" -ClassName BatteryStatus -CimSession $cimSession).ChargeRate if (-not $chargingRate) { return @{ title = "Battery" content = "${chargingEmoji}${batteryLevel}% (Charging)" } } if ($percentage -ne 100) { $remainingToCharge = $fullChargeCapacity - $currentCharge $timeToFullCharge = $remainingToCharge / $chargingRate * 60 $hours = [math]::Floor($timeToFullCharge / 60) $minutes = [math]::Ceiling($timeToFullCharge % 60) if ($hours -eq 0) { $timeToCharge = "$minutes" + "min to $chargingEmoji" } else { $timeToCharge = "$hours" + "h " + "$minutes" + "min to $chargingEmoji" } } return @{ title = "Battery" content = "${batteryEmojiFull}${batteryLevel}% (${timeToCharge})" } } elseif ($battery.PowerLineStatus -like '*Online*') { return @{ title = "Battery" content = "${chargingEmoji}${batteryLevel}% (Plugged in)" } } else { $timeRemaining = $battery.BatteryLifeRemaining / 60 # Don't show time remaining if Windows hasn't properly reported it yet $timeFormatted = if ($timeRemaining -ge 0) { $hours = [math]::floor($timeRemaining / 60) $minutes = [math]::floor($timeRemaining % 60) if ($hours -eq 0) { "${minutes}min" } else { "${hours}h ${minutes}min" } } $batteryReport = if ($batteryLevel -ge 30) { $batteryEmojiFull } else { $batteryEmojiLow } return @{ title = "Battery" content = "${batteryReport}${batteryLevel}% (${timeFormatted} left)" } } } function info_battery_health { $batteryData = Get-CimInstance -ClassName BatteryFullChargedCapacity -Namespace ROOT\WMI -CimSession $cimSession -Property FullChargedCapacity -ErrorAction SilentlyContinue $batteryStatic = Get-CimInstance -ClassName BatteryStaticData -Namespace ROOT\WMI -CimSession $cimSession -Property DesignedCapacity -ErrorAction SilentlyContinue # Exit if battery data is unavailable if (-not $batteryData -or -not $batteryStatic) { return @{ title = "Battery Health" content = "Battery health data unavailable" } } # Convert capacities from mWh to Wh $fullChargeWh = [math]::Round($batteryData.FullChargedCapacity / 1000, 1) $designCapacityWh = [math]::Round($batteryStatic.DesignedCapacity / 1000, 1) # Calculate battery health, handling division by zero $batteryHealth = if ($designCapacityWh -gt 0) { [math]::Floor(($fullChargeWh / $designCapacityWh) * 100) } else { 0 } return @{ title = "Battery Health" content = get_level_info "" $batteryhealthstyle $batteryHealth "$fullChargeWh Wh / $designCapacityWh Wh" -reverse $true } } # ===== LOCALE ===== # Hashtables for language and region codes $localeLookup = @{ "10" = "American Samoa"; "100" = "Guinea"; "10026358" = "Americas"; "10028789" = "Åland Islands"; "10039880" = "Caribbean"; "10039882" = "Northern Europe"; "10039883" = "Southern Africa"; "101" = "Guyana"; "10210824" = "Western Europe"; "10210825" = "Australia and New Zealand"; "103" = "Haiti"; "104" = "Hong Kong SAR"; "10541" = "Europe"; "106" = "Honduras"; "108" = "Croatia"; "109" = "Hungary"; "11" = "Argentina"; "110" = "Iceland"; "111" = "Indonesia"; "113" = "India"; "114" = "British Indian Ocean Territory"; "116" = "Iran"; "117" = "Israel"; "118" = "Italy"; "119" = "Côte d'Ivoire"; "12" = "Australia"; "121" = "Iraq"; "122" = "Japan"; "124" = "Jamaica"; "125" = "Jan Mayen"; "126" = "Jordan"; "127" = "Johnston Atoll"; "129" = "Kenya"; "130" = "Kyrgyzstan"; "131" = "North Korea"; "133" = "Kiribati"; "134" = "Korea"; "136" = "Kuwait"; "137" = "Kazakhstan"; "138" = "Laos"; "139" = "Lebanon"; "14" = "Austria"; "140" = "Latvia"; "141" = "Lithuania"; "142" = "Liberia"; "143" = "Slovakia"; "145" = "Liechtenstein"; "146" = "Lesotho"; "147" = "Luxembourg"; "148" = "Libya"; "149" = "Madagascar"; "151" = "Macao SAR"; "15126" = "Isle of Man"; "152" = "Moldova"; "154" = "Mongolia"; "156" = "Malawi"; "157" = "Mali"; "158" = "Monaco"; "159" = "Morocco"; "160" = "Mauritius"; "161832015" = "Saint Barthélemy"; "161832256" = "U.S. Minor Outlying Islands"; "161832257" = "Latin America and the Caribbean"; "161832258" = "Bonaire, Sint Eustatius and Saba"; "162" = "Mauritania"; "163" = "Malta"; "164" = "Oman"; "165" = "Maldives"; "166" = "Mexico"; "167" = "Malaysia"; "168" = "Mozambique"; "17" = "Bahrain"; "173" = "Niger"; "174" = "Vanuatu"; "175" = "Nigeria"; "176" = "Netherlands"; "177" = "Norway"; "178" = "Nepal"; "18" = "Barbados"; "180" = "Nauru"; "181" = "Suriname"; "182" = "Nicaragua"; "183" = "New Zealand"; "184" = "Palestinian Authority"; "185" = "Paraguay"; "187" = "Peru"; "19" = "Botswana"; "190" = "Pakistan"; "191" = "Poland"; "192" = "Panama"; "193" = "Portugal"; "194" = "Papua New Guinea"; "195" = "Palau"; "196" = "Guinea-Bissau"; "19618" = "North Macedonia"; "197" = "Qatar"; "198" = "Réunion"; "199" = "Marshall Islands"; "2" = "Antigua and Barbuda"; "20" = "Bermuda"; "200" = "Romania"; "201" = "Philippines"; "202" = "Puerto Rico"; "203" = "Russia"; "204" = "Rwanda"; "205" = "Saudi Arabia"; "206" = "Saint Pierre and Miquelon"; "207" = "Saint Kitts and Nevis"; "208" = "Seychelles"; "209" = "South Africa"; "20900" = "Melanesia"; "21" = "Belgium"; "210" = "Senegal"; "212" = "Slovenia"; "21206" = "Micronesia"; "21242" = "Midway Islands"; "2129" = "Asia"; "213" = "Sierra Leone"; "214" = "San Marino"; "215" = "Singapore"; "216" = "Somalia"; "217" = "Spain"; "218" = "Saint Lucia"; "219" = "Sudan"; "22" = "Bahamas"; "220" = "Svalbard"; "221" = "Sweden"; "222" = "Syria"; "223" = "Switzerland"; "224" = "United Arab Emirates"; "225" = "Trinidad and Tobago"; "227" = "Thailand"; "228" = "Tajikistan"; "23" = "Bangladesh"; "231" = "Tonga"; "232" = "Togo"; "233" = "São Tomé and Príncipe"; "234" = "Tunisia"; "235" = "Turkey"; "23581" = "Northern America"; "236" = "Tuvalu"; "237" = "Taiwan"; "238" = "Turkmenistan"; "239" = "Tanzania"; "24" = "Belize"; "240" = "Uganda"; "241" = "Ukraine"; "242" = "United Kingdom"; "244" = "United States"; "245" = "Burkina Faso"; "246" = "Uruguay"; "247" = "Uzbekistan"; "248" = "Saint Vincent and the Grenadines"; "249" = "Venezuela"; "25" = "Bosnia and Herzegovina"; "251" = "Vietnam"; "252" = "U.S. Virgin Islands"; "253" = "Vatican City"; "254" = "Namibia"; "258" = "Wake Island"; "259" = "Samoa"; "26" = "Bolivia"; "260" = "Swaziland"; "261" = "Yemen"; "26286" = "Polynesia"; "263" = "Zambia"; "264" = "Zimbabwe"; "269" = "Serbia and Montenegro (Former)"; "27" = "Myanmar"; "270" = "Montenegro"; "27082" = "Central America"; "271" = "Serbia"; "27114" = "Oceania"; "273" = "Curaçao"; "276" = "South Sudan"; "28" = "Benin"; "29" = "Belarus"; "3" = "Afghanistan"; "30" = "Solomon Islands"; "300" = "Anguilla"; "301" = "Antarctica"; "302" = "Aruba"; "303" = "Ascension Island"; "304" = "Ashmore and Cartier Islands"; "305" = "Baker Island"; "306" = "Bouvet Island"; "307" = "Cayman Islands"; "308" = "Channel Islands"; "309" = "Christmas Island"; "30967" = "Sint Maarten"; "310" = "Clipperton Island"; "311" = "Cocos (Keeling) Islands"; "312" = "Cook Islands"; "313" = "Coral Sea Islands"; "31396" = "South America"; "314" = "Diego Garcia"; "315" = "Falkland Islands"; "317" = "French Guiana"; "31706" = "Saint Martin"; "318" = "French Polynesia"; "319" = "French Southern Territories"; "32" = "Brazil"; "321" = "Guadeloupe"; "322" = "Guam"; "323" = "Guantanamo Bay"; "324" = "Guernsey"; "325" = "Heard Island and McDonald Islands"; "326" = "Howland Island"; "327" = "Jarvis Island"; "328" = "Jersey"; "329" = "Kingman Reef"; "330" = "Martinique"; "331" = "Mayotte"; "332" = "Montserrat"; "333" = "Netherlands Antilles (Former)"; "334" = "New Caledonia"; "335" = "Niue"; "336" = "Norfolk Island"; "337" = "Northern Mariana Islands"; "338" = "Palmyra Atoll"; "339" = "Pitcairn Islands"; "34" = "Bhutan"; "340" = "Rota Island"; "341" = "Saipan"; "342" = "South Georgia and the South Sandwich Islands"; "343" = "St Helena, Ascension and Tristan da Cunha"; "346" = "Tinian Island"; "347" = "Tokelau"; "348" = "Tristan da Cunha"; "349" = "Turks and Caicos Islands"; "35" = "Bulgaria"; "351" = "British Virgin Islands"; "352" = "Wallis and Futuna"; "37" = "Brunei"; "38" = "Burundi"; "39" = "Canada"; "39070" = "World"; "4" = "Algeria"; "40" = "Cambodia"; "41" = "Chad"; "42" = "Sri Lanka"; "42483" = "Western Africa"; "42484" = "Middle Africa"; "42487" = "Northern Africa"; "43" = "Congo"; "44" = "Congo (DRC)"; "45" = "China"; "46" = "Chile"; "47590" = "Central Asia"; "47599" = "South-Eastern Asia"; "47600" = "Eastern Asia"; "47603" = "Eastern Africa"; "47609" = "Eastern Europe"; "47610" = "Southern Europe"; "47611" = "Middle East"; "47614" = "Southern Asia"; "49" = "Cameroon"; "5" = "Azerbaijan"; "50" = "Comoros"; "51" = "Colombia"; "54" = "Costa Rica"; "55" = "Central African Republic"; "56" = "Cuba"; "57" = "Cabo Verde"; "59" = "Cyprus"; "6" = "Albania"; "61" = "Denmark"; "62" = "Djibouti"; "63" = "Dominica"; "65" = "Dominican Republic"; "66" = "Ecuador"; "67" = "Egypt"; "68" = "Ireland"; "69" = "Equatorial Guinea"; "7" = "Armenia"; "70" = "Estonia"; "71" = "Eritrea"; "72" = "El Salvador"; "7299303" = "Timor-Leste"; "73" = "Ethiopia"; "742" = "Africa"; "75" = "Czech Republic"; "77" = "Finland"; "78" = "Fiji"; "8" = "Andorra"; "80" = "Micronesia"; "81" = "Faroe Islands"; "84" = "France"; "86" = "Gambia"; "87" = "Gabon"; "88" = "Georgia"; "89" = "Ghana"; "9" = "Angola"; "90" = "Gibraltar"; "91" = "Grenada"; "93" = "Greenland"; "94" = "Germany"; "98" = "Greece"; "99" = "Guatemala"; "9914689" = "Kosovo"; } $languageLookup = @{ "aa" = "Afar"; "aa-DJ" = "Afar (Djibouti)"; "aa-ER" = "Afar (Eritrea)"; "aa-ET" = "Afar (Ethiopia)"; "af" = "Afrikaans"; "af-NA" = "Afrikaans (Namibia)"; "af-ZA" = "Afrikaans (South Africa)"; "agq" = "Aghem"; "agq-CM" = "Aghem (Cameroon)"; "ak" = "Akan"; "ak-GH" = "Akan (Ghana)"; "am" = "Amharic"; "am-ET" = "Amharic (Ethiopia)"; "ar" = "Arabic"; "ar-001" = "Arabic (World)"; "ar-AE" = "Arabic (U.A.E.)"; "ar-BH" = "Arabic (Bahrain)"; "ar-DJ" = "Arabic (Djibouti)"; "ar-DZ" = "Arabic (Algeria)"; "ar-EG" = "Arabic (Egypt)"; "ar-ER" = "Arabic (Eritrea)"; "ar-IL" = "Arabic (Israel)"; "ar-IQ" = "Arabic (Iraq)"; "ar-JO" = "Arabic (Jordan)"; "ar-KM" = "Arabic (Comoros)"; "ar-KW" = "Arabic (Kuwait)"; "ar-LB" = "Arabic (Lebanon)"; "ar-LY" = "Arabic (Libya)"; "ar-MA" = "Arabic (Morocco)"; "ar-MR" = "Arabic (Mauritania)"; "ar-OM" = "Arabic (Oman)"; "ar-PS" = "Arabic (Palestinian Authority)"; "ar-QA" = "Arabic (Qatar)"; "ar-SA" = "Arabic (Saudi Arabia)"; "ar-SD" = "Arabic (Sudan)"; "ar-SO" = "Arabic (Somalia)"; "ar-SS" = "Arabic (South Sudan)"; "ar-SY" = "Arabic (Syria)"; "ar-TD" = "Arabic (Chad)"; "ar-TN" = "Arabic (Tunisia)"; "ar-YE" = "Arabic (Yemen)"; "arn" = "Mapudungun"; "arn-CL" = "Mapudungun (Chile)"; "as" = "Assamese"; "as-IN" = "Assamese (India)"; "asa" = "Asu"; "asa-TZ" = "Asu (Tanzania)"; "ast" = "Asturian"; "ast-ES" = "Asturian (Spain)"; "az" = "Azerbaijani"; "az-Cyrl" = "Azerbaijani (Cyrillic)"; "az-Cyrl-AZ" = "Azerbaijani (Cyrillic, Azerbaijan)"; "az-Latn" = "Azerbaijani (Latin)"; "az-Latn-AZ" = "Azerbaijani (Latin, Azerbaijan)"; "ba" = "Bashkir"; "ba-RU" = "Bashkir (Russia)"; "bas" = "Basaa"; "bas-CM" = "Basaa (Cameroon)"; "be" = "Belarusian"; "be-BY" = "Belarusian (Belarus)"; "bem" = "Bemba"; "bem-ZM" = "Bemba (Zambia)"; "bez" = "Bena"; "bez-TZ" = "Bena (Tanzania)"; "bg" = "Bulgarian"; "bg-BG" = "Bulgarian (Bulgaria)"; "bin" = "Edo"; "bin-NG" = "Edo (Nigeria)"; "bm" = "Bambara"; "bm-Latn" = "Bambara (Latin)"; "bm-Latn-ML" = "Bambara (Latin, Mali)"; "bn" = "Bangla"; "bn-BD" = "Bangla (Bangladesh)"; "bn-IN" = "Bangla (India)"; "bo" = "Tibetan"; "bo-CN" = "Tibetan (PRC)"; "bo-IN" = "Tibetan (India)"; "br" = "Breton"; "br-FR" = "Breton (France)"; "brx" = "Bodo"; "brx-IN" = "Bodo (India)"; "bs" = "Bosnian"; "bs-Cyrl" = "Bosnian (Cyrillic)"; "bs-Cyrl-BA" = "Bosnian (Cyrillic, Bosnia and Herzegovina)"; "bs-Latn" = "Bosnian (Latin)"; "bs-Latn-BA" = "Bosnian (Latin, Bosnia and Herzegovina)"; "byn" = "Blin"; "byn-ER" = "Blin (Eritrea)"; "ca" = "Catalan"; "ca-AD" = "Catalan (Andorra)"; "ca-ES" = "Catalan (Catalan)"; "ca-ES-valencia" = "Valencian (Spain)"; "ca-FR" = "Catalan (France)"; "ca-IT" = "Catalan (Italy)"; "ce" = "Chechen"; "ce-RU" = "Chechen (Russia)"; "cgg" = "Chiga"; "cgg-UG" = "Chiga (Uganda)"; "chr" = "Cherokee"; "chr-Cher" = "Cherokee (Cherokee)"; "chr-Cher-US" = "Cherokee (Cherokee)"; "co" = "Corsican"; "co-FR" = "Corsican (France)"; "cs" = "Czech"; "cs-CZ" = "Czech (Czechia / Czech Republic)"; "cu" = "Church Slavic"; "cu-RU" = "Church Slavic (Russia)"; "cy" = "Welsh"; "cy-GB" = "Welsh (United Kingdom)"; "da" = "Danish"; "da-DK" = "Danish (Denmark)"; "da-GL" = "Danish (Greenland)"; "dav" = "Taita"; "dav-KE" = "Taita (Kenya)"; "de" = "German"; "de-AT" = "German (Austria)"; "de-BE" = "German (Belgium)"; "de-CH" = "German (Switzerland)"; "de-DE" = "German (Germany)"; "de-IT" = "German (Italy)"; "de-LI" = "German (Liechtenstein)"; "de-LU" = "German (Luxembourg)"; "dje" = "Zarma"; "dje-NE" = "Zarma (Niger)"; "dsb" = "Lower Sorbian"; "dsb-DE" = "Lower Sorbian (Germany)"; "dua" = "Duala"; "dua-CM" = "Duala (Cameroon)"; "dv" = "Divehi"; "dv-MV" = "Divehi (Maldives)"; "dyo" = "Jola-Fonyi"; "dyo-SN" = "Jola-Fonyi (Senegal)"; "dz" = "Dzongkha"; "dz-BT" = "Dzongkha (Bhutan)"; "ebu" = "Embu"; "ebu-KE" = "Embu (Kenya)"; "ee" = "Ewe"; "ee-GH" = "Ewe (Ghana)"; "ee-TG" = "Ewe (Togo)"; "el" = "Greek"; "el-CY" = "Greek (Cyprus)"; "el-GR" = "Greek (Greece)"; "en" = "English"; "en-001" = "English (World)"; "en-029" = "English (Caribbean)"; "en-150" = "English (Europe)"; "en-AG" = "English (Antigua and Barbuda)"; "en-AI" = "English (Anguilla)"; "en-AS" = "English (American Samoa)"; "en-AT" = "English (Austria)"; "en-AU" = "English (Australia)"; "en-BB" = "English (Barbados)"; "en-BE" = "English (Belgium)"; "en-BI" = "English (Burundi)"; "en-BM" = "English (Bermuda)"; "en-BS" = "English (Bahamas)"; "en-BW" = "English (Botswana)"; "en-BZ" = "English (Belize)"; "en-CA" = "English (Canada)"; "en-CC" = "English (Cocos [Keeling] Islands)"; "en-CH" = "English (Switzerland)"; "en-CK" = "English (Cook Islands)"; "en-CM" = "English (Cameroon)"; "en-CX" = "English (Christmas Island)"; "en-CY" = "English (Cyprus)"; "en-DE" = "English (Germany)"; "en-DK" = "English (Denmark)"; "en-DM" = "English (Dominica)"; "en-ER" = "English (Eritrea)"; "en-FI" = "English (Finland)"; "en-FJ" = "English (Fiji)"; "en-FK" = "English (Falkland Islands)"; "en-FM" = "English (Micronesia)"; "en-GB" = "English (United Kingdom)"; "en-GD" = "English (Grenada)"; "en-GG" = "English (Guernsey)"; "en-GH" = "English (Ghana)"; "en-GI" = "English (Gibraltar)"; "en-GM" = "English (Gambia)"; "en-GU" = "English (Guam)"; "en-GY" = "English (Guyana)"; "en-HK" = "English (Hong Kong SAR)"; "en-ID" = "English (Indonesia)"; "en-IE" = "English (Ireland)"; "en-IL" = "English (Israel)"; "en-IM" = "English (Isle of Man)"; "en-IN" = "English (India)"; "en-IO" = "English (British Indian Ocean Territory)"; "en-JE" = "English (Jersey)"; "en-JM" = "English (Jamaica)"; "en-KE" = "English (Kenya)"; "en-KI" = "English (Kiribati)"; "en-KN" = "English (Saint Kitts and Nevis)"; "en-KY" = "English (Cayman Islands)"; "en-LC" = "English (Saint Lucia)"; "en-LR" = "English (Liberia)"; "en-LS" = "English (Lesotho)"; "en-MG" = "English (Madagascar)"; "en-MH" = "English (Marshall Islands)"; "en-MO" = "English (Macao SAR)"; "en-MP" = "English (Northern Mariana Islands)"; "en-MS" = "English (Montserrat)"; "en-MT" = "English (Malta)"; "en-MU" = "English (Mauritius)"; "en-MW" = "English (Malawi)"; "en-MY" = "English (Malaysia)"; "en-NA" = "English (Namibia)"; "en-NF" = "English (Norfolk Island)"; "en-NG" = "English (Nigeria)"; "en-NL" = "English (Netherlands)"; "en-NR" = "English (Nauru)"; "en-NU" = "English (Niue)"; "en-NZ" = "English (New Zealand)"; "en-PG" = "English (Papua New Guinea)"; "en-PH" = "English (Philippines)"; "en-PK" = "English (Pakistan)"; "en-PN" = "English (Pitcairn Islands)"; "en-PR" = "English (Puerto Rico)"; "en-PW" = "English (Palau)"; "en-RW" = "English (Rwanda)"; "en-SB" = "English (Solomon Islands)"; "en-SC" = "English (Seychelles)"; "en-SD" = "English (Sudan)"; "en-SE" = "English (Sweden)"; "en-SG" = "English (Singapore)"; "en-SH" = "English (St Helena, Ascension, Tristan da Cunha)"; "en-SI" = "English (Slovenia)"; "en-SL" = "English (Sierra Leone)"; "en-SS" = "English (South Sudan)"; "en-SX" = "English (Sint Maarten)"; "en-SZ" = "English (Swaziland)"; "en-TC" = "English (Turks and Caicos Islands)"; "en-TK" = "English (Tokelau)"; "en-TO" = "English (Tonga)"; "en-TT" = "English (Trinidad and Tobago)"; "en-TV" = "English (Tuvalu)"; "en-TZ" = "English (Tanzania)"; "en-UG" = "English (Uganda)"; "en-UM" = "English (US Minor Outlying Islands)"; "en-US" = "English (United States)"; "en-VC" = "English (Saint Vincent and the Grenadines)"; "en-VG" = "English (British Virgin Islands)"; "en-VI" = "English (US Virgin Islands)"; "en-VU" = "English (Vanuatu)"; "en-WS" = "English (Samoa)"; "en-ZA" = "English (South Africa)"; "en-ZM" = "English (Zambia)"; "en-ZW" = "English (Zimbabwe)"; "eo" = "Esperanto"; "eo-001" = "Esperanto (World)"; "es" = "Spanish"; "es-419" = "Spanish (Latin America)"; "es-AR" = "Spanish (Argentina)"; "es-BO" = "Spanish (Bolivia)"; "es-BR" = "Spanish (Brazil)"; "es-BZ" = "Spanish (Belize)"; "es-CL" = "Spanish (Chile)"; "es-CO" = "Spanish (Colombia)"; "es-CR" = "Spanish (Costa Rica)"; "es-CU" = "Spanish (Cuba)"; "es-DO" = "Spanish (Dominican Republic)"; "es-EC" = "Spanish (Ecuador)"; "es-ES" = "Spanish (Spain)"; "es-GQ" = "Spanish (Equatorial Guinea)"; "es-GT" = "Spanish (Guatemala)"; "es-HN" = "Spanish (Honduras)"; "es-MX" = "Spanish (Mexico)"; "es-NI" = "Spanish (Nicaragua)"; "es-PA" = "Spanish (Panama)"; "es-PE" = "Spanish (Peru)"; "es-PH" = "Spanish (Philippines)"; "es-PR" = "Spanish (Puerto Rico)"; "es-PY" = "Spanish (Paraguay)"; "es-SV" = "Spanish (El Salvador)"; "es-US" = "Spanish (United States)"; "es-UY" = "Spanish (Uruguay)"; "es-VE" = "Spanish (Venezuela)"; "et" = "Estonian"; "et-EE" = "Estonian (Estonia)"; "eu" = "Basque"; "eu-ES" = "Basque (Basque)"; "ewo" = "Ewondo"; "ewo-CM" = "Ewondo (Cameroon)"; "fa" = "Persian"; "fa-IR" = "Persian (Iran)"; "ff" = "Fulah"; "ff-CM" = "Fulah (Cameroon)"; "ff-GN" = "Fulah (Guinea)"; "ff-Latn" = "Fulah (Latin)"; "ff-Latn-SN" = "Fulah (Latin, Senegal)"; "ff-MR" = "Fulah (Mauritania)"; "ff-NG" = "Fulah (Nigeria)"; "fi" = "Finnish"; "fi-FI" = "Finnish (Finland)"; "fil" = "Filipino"; "fil-PH" = "Filipino (Philippines)"; "fo" = "Faroese"; "fo-DK" = "Faroese (Denmark)"; "fo-FO" = "Faroese (Faroe Islands)"; "fr" = "French"; "fr-029" = "French (Caribbean)"; "fr-BE" = "French (Belgium)"; "fr-BF" = "French (Burkina Faso)"; "fr-BI" = "French (Burundi)"; "fr-BJ" = "French (Benin)"; "fr-BL" = "French (Saint Barthélemy)"; "fr-CA" = "French (Canada)"; "fr-CD" = "French (Congo DRC)"; "fr-CF" = "French (Central African Republic)"; "fr-CG" = "French (Congo)"; "fr-CH" = "French (Switzerland)"; "fr-CI" = "French (Côte d’Ivoire)"; "fr-CM" = "French (Cameroon)"; "fr-DJ" = "French (Djibouti)"; "fr-DZ" = "French (Algeria)"; "fr-FR" = "French (France)"; "fr-GA" = "French (Gabon)"; "fr-GF" = "French (French Guiana)"; "fr-GN" = "French (Guinea)"; "fr-GP" = "French (Guadeloupe)"; "fr-GQ" = "French (Equatorial Guinea)"; "fr-HT" = "French (Haiti)"; "fr-KM" = "French (Comoros)"; "fr-LU" = "French (Luxembourg)"; "fr-MA" = "French (Morocco)"; "fr-MC" = "French (Monaco)"; "fr-MF" = "French (Saint Martin)"; "fr-MG" = "French (Madagascar)"; "fr-ML" = "French (Mali)"; "fr-MQ" = "French (Martinique)"; "fr-MR" = "French (Mauritania)"; "fr-MU" = "French (Mauritius)"; "fr-NC" = "French (New Caledonia)"; "fr-NE" = "French (Niger)"; "fr-PF" = "French (French Polynesia)"; "fr-PM" = "French (Saint Pierre and Miquelon)"; "fr-RE" = "French (Reunion)"; "fr-RW" = "French (Rwanda)"; "fr-SC" = "French (Seychelles)"; "fr-SN" = "French (Senegal)"; "fr-SY" = "French (Syria)"; "fr-TD" = "French (Chad)"; "fr-TG" = "French (Togo)"; "fr-TN" = "French (Tunisia)"; "fr-VU" = "French (Vanuatu)"; "fr-WF" = "French (Wallis and Futuna)"; "fr-YT" = "French (Mayotte)"; "fur" = "Friulian"; "fur-IT" = "Friulian (Italy)"; "fy" = "Frisian"; "fy-NL" = "Frisian (Netherlands)"; "ga" = "Irish"; "ga-IE" = "Irish (Ireland)"; "gd" = "Scottish Gaelic"; "gd-GB" = "Scottish Gaelic (United Kingdom)"; "gl" = "Galician"; "gl-ES" = "Galician (Galician)"; "gn" = "Guarani"; "gn-PY" = "Guarani (Paraguay)"; "gsw" = "Alsatian"; "gsw-CH" = "Alsatian (Switzerland)"; "gsw-FR" = "Alsatian (France)"; "gsw-LI" = "Alsatian (Liechtenstein)"; "gu" = "Gujarati"; "gu-IN" = "Gujarati (India)"; "guz" = "Gusii"; "guz-KE" = "Gusii (Kenya)"; "gv" = "Manx"; "gv-IM" = "Manx (Isle of Man)"; "ha" = "Hausa"; "ha-Latn" = "Hausa (Latin)"; "ha-Latn-GH" = "Hausa (Latin, Ghana)"; "ha-Latn-NE" = "Hausa (Latin, Niger)"; "ha-Latn-NG" = "Hausa (Latin, Nigeria)"; "haw" = "Hawaiian"; "haw-US" = "Hawaiian (United States)"; "he" = "Hebrew"; "he-IL" = "Hebrew (Israel)"; "hi" = "Hindi"; "hi-IN" = "Hindi (India)"; "hr" = "Croatian"; "hr-BA" = "Croatian (Latin, Bosnia and Herzegovina)"; "hr-HR" = "Croatian (Croatia)"; "hsb" = "Upper Sorbian"; "hsb-DE" = "Upper Sorbian (Germany)"; "hu" = "Hungarian"; "hu-HU" = "Hungarian (Hungary)"; "hy" = "Armenian"; "hy-AM" = "Armenian (Armenia)"; "ia" = "Interlingua"; "ia-001" = "Interlingua (World)"; "ia-FR" = "Interlingua (France)"; "ibb" = "Ibibio"; "ibb-NG" = "Ibibio (Nigeria)"; "id" = "Indonesian"; "id-ID" = "Indonesian (Indonesia)"; "ig" = "Igbo"; "ig-NG" = "Igbo (Nigeria)"; "ii" = "Yi"; "ii-CN" = "Yi (PRC)"; "is" = "Icelandic"; "is-IS" = "Icelandic (Iceland)"; "it" = "Italian"; "it-CH" = "Italian (Switzerland)"; "it-IT" = "Italian (Italy)"; "it-SM" = "Italian (San Marino)"; "it-VA" = "Italian (Vatican City)"; "iu" = "Inuktitut"; "iu-Cans" = "Inuktitut (Syllabics)"; "iu-Cans-CA" = "Inuktitut (Syllabics, Canada)"; "iu-Latn" = "Inuktitut (Latin)"; "iu-Latn-CA" = "Inuktitut (Latin, Canada)"; "ja" = "Japanese"; "ja-JP" = "Japanese (Japan)"; "jgo" = "Ngomba"; "jgo-CM" = "Ngomba (Cameroon)"; "jmc" = "Machame"; "jmc-TZ" = "Machame (Tanzania)"; "jv" = "Javanese"; "jv-Java" = "Javanese (Javanese)"; "jv-Java-ID" = "Javanese (Javanese, Indonesia)"; "jv-Latn" = "Javanese"; "jv-Latn-ID" = "Javanese (Indonesia)"; "ka" = "Georgian"; "ka-GE" = "Georgian (Georgia)"; "kab" = "Kabyle"; "kab-DZ" = "Kabyle (Algeria)"; "kam" = "Kamba"; "kam-KE" = "Kamba (Kenya)"; "kde" = "Makonde"; "kde-TZ" = "Makonde (Tanzania)"; "kea" = "Kabuverdianu"; "kea-CV" = "Kabuverdianu (Cabo Verde)"; "khq" = "Koyra Chiini"; "khq-ML" = "Koyra Chiini (Mali)"; "ki" = "Kikuyu"; "ki-KE" = "Kikuyu (Kenya)"; "kk" = "Kazakh"; "kk-KZ" = "Kazakh (Kazakhstan)"; "kkj" = "Kako"; "kkj-CM" = "Kako (Cameroon)"; "kl" = "Greenlandic"; "kl-GL" = "Greenlandic (Greenland)"; "kln" = "Kalenjin"; "kln-KE" = "Kalenjin (Kenya)"; "km" = "Khmer"; "km-KH" = "Khmer (Cambodia)"; "kn" = "Kannada"; "kn-IN" = "Kannada (India)"; "ko" = "Korean"; "ko-KP" = "Korean (North Korea)"; "ko-KR" = "Korean (Korea)"; "kok" = "Konkani"; "kok-IN" = "Konkani (India)"; "kr" = "Kanuri"; "kr-NG" = "Kanuri (Nigeria)"; "ks" = "Kashmiri"; "ks-Arab" = "Kashmiri (Perso-Arabic)"; "ks-Arab-IN" = "Kashmiri (Perso-Arabic)"; "ks-Deva" = "Kashmiri (Devanagari)"; "ks-Deva-IN" = "Kashmiri (Devanagari, India)"; "ksb" = "Shambala"; "ksb-TZ" = "Shambala (Tanzania)"; "ksf" = "Bafia"; "ksf-CM" = "Bafia (Cameroon)"; "ksh" = "Colognian"; "ksh-DE" = "Ripuarian (Germany)"; "ku" = "Central Kurdish"; "ku-Arab" = "Central Kurdish (Arabic)"; "ku-Arab-IQ" = "Central Kurdish (Iraq)"; "ku-Arab-IR" = "Kurdish (Perso-Arabic, Iran)"; "kw" = "Cornish"; "kw-GB" = "Cornish (United Kingdom)"; "ky" = "Kyrgyz"; "ky-KG" = "Kyrgyz (Kyrgyzstan)"; "la" = "Latin"; "la-001" = "Latin (World)"; "lag" = "Langi"; "lag-TZ" = "Langi (Tanzania)"; "lb" = "Luxembourgish"; "lb-LU" = "Luxembourgish (Luxembourg)"; "lg" = "Ganda"; "lg-UG" = "Ganda (Uganda)"; "lkt" = "Lakota"; "lkt-US" = "Lakota (United States)"; "ln" = "Lingala"; "ln-AO" = "Lingala (Angola)"; "ln-CD" = "Lingala (Congo DRC)"; "ln-CF" = "Lingala (Central African Republic)"; "ln-CG" = "Lingala (Congo)"; "lo" = "Lao"; "lo-LA" = "Lao (Lao P.D.R.)"; "lrc" = "Northern Luri"; "lrc-IQ" = "Northern Luri (Iraq)"; "lrc-IR" = "Northern Luri (Iran)"; "lt" = "Lithuanian"; "lt-LT" = "Lithuanian (Lithuania)"; "lu" = "Luba-Katanga"; "lu-CD" = "Luba-Katanga (Congo DRC)"; "luo" = "Luo"; "luo-KE" = "Luo (Kenya)"; "luy" = "Luyia"; "luy-KE" = "Luyia (Kenya)"; "lv" = "Latvian"; "lv-LV" = "Latvian (Latvia)"; "mas" = "Masai"; "mas-KE" = "Masai (Kenya)"; "mas-TZ" = "Masai (Tanzania)"; "mer" = "Meru"; "mer-KE" = "Meru (Kenya)"; "mfe" = "Morisyen"; "mfe-MU" = "Morisyen (Mauritius)"; "mg" = "Malagasy"; "mg-MG" = "Malagasy (Madagascar)"; "mgh" = "Makhuwa-Meetto"; "mgh-MZ" = "Makhuwa-Meetto (Mozambique)"; "mgo" = "Meta'"; "mgo-CM" = "Meta' (Cameroon)"; "mi" = "Maori"; "mi-NZ" = "Maori (New Zealand)"; "mk" = "Macedonian (FYROM)"; "mk-MK" = "Macedonian (Former Yugoslav Republic of Macedonia)"; "ml" = "Malayalam"; "ml-IN" = "Malayalam (India)"; "mn" = "Mongolian"; "mn-Cyrl" = "Mongolian (Cyrillic)"; "mn-MN" = "Mongolian (Cyrillic, Mongolia)"; "mn-Mong" = "Mongolian (Traditional Mongolian)"; "mn-Mong-CN" = "Mongolian (Traditional Mongolian, PRC)"; "mn-Mong-MN" = "Mongolian (Traditional Mongolian, Mongolia)"; "mni" = "Manipuri"; "mni-IN" = "Manipuri (India)"; "moh" = "Mohawk"; "moh-CA" = "Mohawk (Mohawk)"; "mr" = "Marathi"; "mr-IN" = "Marathi (India)"; "ms" = "Malay"; "ms-BN" = "Malay (Brunei Darussalam)"; "ms-MY" = "Malay (Malaysia)"; "ms-SG" = "Malay (Latin, Singapore)"; "mt" = "Maltese"; "mt-MT" = "Maltese (Malta)"; "mua" = "Mundang"; "mua-CM" = "Mundang (Cameroon)"; "my" = "Burmese"; "my-MM" = "Burmese (Myanmar)"; "mzn" = "Mazanderani"; "mzn-IR" = "Mazanderani (Iran)"; "naq" = "Nama"; "naq-NA" = "Nama (Namibia)"; "nb" = "Norwegian (Bokmål)"; "nb-NO" = "Norwegian, Bokmål (Norway)"; "nb-SJ" = "Norwegian, Bokmål (Svalbard and Jan Mayen)"; "nd" = "North Ndebele"; "nd-ZW" = "North Ndebele (Zimbabwe)"; "nds" = "Low German"; "nds-DE" = "Low German (Germany)"; "nds-NL" = "Low German (Netherlands)"; "ne" = "Nepali"; "ne-IN" = "Nepali (India)"; "ne-NP" = "Nepali (Nepal)"; "nl" = "Dutch"; "nl-AW" = "Dutch (Aruba)"; "nl-BE" = "Dutch (Belgium)"; "nl-BQ" = "Dutch (Bonaire, Sint Eustatius and Saba)"; "nl-CW" = "Dutch (Curaçao)"; "nl-NL" = "Dutch (Netherlands)"; "nl-SR" = "Dutch (Suriname)"; "nl-SX" = "Dutch (Sint Maarten)"; "nmg" = "Kwasio"; "nmg-CM" = "Kwasio (Cameroon)"; "nn" = "Norwegian (Nynorsk)"; "nn-NO" = "Norwegian, Nynorsk (Norway)"; "nnh" = "Ngiemboon"; "nnh-CM" = "Ngiemboon (Cameroon)"; "no" = "Norwegian"; "nqo" = "N'ko"; "nqo-GN" = "N'ko (Guinea)"; "nr" = "South Ndebele"; "nr-ZA" = "South Ndebele (South Africa)"; "nso" = "Sesotho sa Leboa"; "nso-ZA" = "Sesotho sa Leboa (South Africa)"; "nus" = "Nuer"; "nus-SS" = "Nuer (South Sudan)"; "nyn" = "Nyankole"; "nyn-UG" = "Nyankole (Uganda)"; "oc" = "Occitan"; "oc-FR" = "Occitan (France)"; "om" = "Oromo"; "om-ET" = "Oromo (Ethiopia)"; "om-KE" = "Oromo (Kenya)"; "or" = "Odia"; "or-IN" = "Odia (India)"; "os" = "Ossetic"; "os-GE" = "Ossetian (Cyrillic, Georgia)"; "os-RU" = "Ossetian (Cyrillic, Russia)"; "pa" = "Punjabi"; "pa-Arab" = "Punjabi (Arabic)"; "pa-Arab-PK" = "Punjabi (Islamic Republic of Pakistan)"; "pa-IN" = "Punjabi (India)"; "pap" = "Papiamento"; "pap-029" = "Papiamento (Caribbean)"; "pl" = "Polish"; "pl-PL" = "Polish (Poland)"; "prg" = "Prussian"; "prg-001" = "Prussian (World)"; "prs" = "Dari"; "prs-AF" = "Dari (Afghanistan)"; "ps" = "Pashto"; "ps-AF" = "Pashto (Afghanistan)"; "pt" = "Portuguese"; "pt-AO" = "Portuguese (Angola)"; "pt-BR" = "Portuguese (Brazil)"; "pt-CH" = "Portuguese (Switzerland)"; "pt-CV" = "Portuguese (Cabo Verde)"; "pt-GQ" = "Portuguese (Equatorial Guinea)"; "pt-GW" = "Portuguese (Guinea-Bissau)"; "pt-LU" = "Portuguese (Luxembourg)"; "pt-MO" = "Portuguese (Macao SAR)"; "pt-MZ" = "Portuguese (Mozambique)"; "pt-PT" = "Portuguese (Portugal)"; "pt-ST" = "Portuguese (São Tomé and Príncipe)"; "pt-TL" = "Portuguese (Timor-Leste)"; "quc" = "K'iche'"; "quc-Latn" = "K'iche'"; "quc-Latn-GT" = "K'iche' (Guatemala)"; "quz" = "Quechua"; "quz-BO" = "Quechua (Bolivia)"; "quz-EC" = "Quechua (Ecuador)"; "quz-PE" = "Quechua (Peru)"; "rm" = "Romansh"; "rm-CH" = "Romansh (Switzerland)"; "rn" = "Rundi"; "rn-BI" = "Rundi (Burundi)"; "ro" = "Romanian"; "ro-MD" = "Romanian (Moldova)"; "ro-RO" = "Romanian (Romania)"; "rof" = "Rombo"; "rof-TZ" = "Rombo (Tanzania)"; "ru" = "Russian"; "ru-BY" = "Russian (Belarus)"; "ru-KG" = "Russian (Kyrgyzstan)"; "ru-KZ" = "Russian (Kazakhstan)"; "ru-MD" = "Russian (Moldova)"; "ru-RU" = "Russian (Russia)"; "ru-UA" = "Russian (Ukraine)"; "rw" = "Kinyarwanda"; "rw-RW" = "Kinyarwanda (Rwanda)"; "rwk" = "Rwa"; "rwk-TZ" = "Rwa (Tanzania)"; "sa" = "Sanskrit"; "sa-IN" = "Sanskrit (India)"; "sah" = "Sakha"; "sah-RU" = "Sakha (Russia)"; "saq" = "Samburu"; "saq-KE" = "Samburu (Kenya)"; "sbp" = "Sangu"; "sbp-TZ" = "Sangu (Tanzania)"; "sd" = "Sindhi"; "sd-Arab" = "Sindhi (Arabic)"; "sd-Arab-PK" = "Sindhi (Islamic Republic of Pakistan)"; "sd-Deva" = "Sindhi (Devanagari)"; "sd-Deva-IN" = "Sindhi (Devanagari, India)"; "se" = "Sami (Northern)"; "se-FI" = "Sami, Northern (Finland)"; "se-NO" = "Sami, Northern (Norway)"; "se-SE" = "Sami, Northern (Sweden)"; "seh" = "Sena"; "seh-MZ" = "Sena (Mozambique)"; "ses" = "Koyraboro Senni"; "ses-ML" = "Koyraboro Senni (Mali)"; "sg" = "Sango"; "sg-CF" = "Sango (Central African Republic)"; "shi" = "Tachelhit"; "shi-Latn" = "Tachelhit (Latin)"; "shi-Latn-MA" = "Tachelhit (Latin, Morocco)"; "shi-Tfng" = "Tachelhit (Tifinagh)"; "shi-Tfng-MA" = "Tachelhit (Tifinagh, Morocco)"; "si" = "Sinhala"; "si-LK" = "Sinhala (Sri Lanka)"; "sk" = "Slovak"; "sk-SK" = "Slovak (Slovakia)"; "sl" = "Slovenian"; "sl-SI" = "Slovenian (Slovenia)"; "sma" = "Sami (Southern)"; "sma-NO" = "Sami, Southern (Norway)"; "sma-SE" = "Sami, Southern (Sweden)"; "smj" = "Sami (Lule)"; "smj-NO" = "Sami, Lule (Norway)"; "smj-SE" = "Sami, Lule (Sweden)"; "smn" = "Sami (Inari)"; "smn-FI" = "Sami, Inari (Finland)"; "sms" = "Sami (Skolt)"; "sms-FI" = "Sami, Skolt (Finland)"; "sn" = "Shona"; "sn-Latn" = "Shona (Latin)"; "sn-Latn-ZW" = "Shona (Latin, Zimbabwe)"; "so" = "Somali"; "so-DJ" = "Somali (Djibouti)"; "so-ET" = "Somali (Ethiopia)"; "so-KE" = "Somali (Kenya)"; "so-SO" = "Somali (Somalia)"; "sq" = "Albanian"; "sq-AL" = "Albanian (Albania)"; "sq-MK" = "Albanian (Macedonia, FYRO)"; "sq-XK" = "Albanian (Kosovo)"; "sr" = "Serbian"; "sr-Cyrl" = "Serbian (Cyrillic)"; "sr-Cyrl-BA" = "Serbian (Cyrillic, Bosnia and Herzegovina)"; "sr-Cyrl-ME" = "Serbian (Cyrillic, Montenegro)"; "sr-Cyrl-RS" = "Serbian (Cyrillic, Serbia)"; "sr-Cyrl-XK" = "Serbian (Cyrillic, Kosovo)"; "sr-Latn" = "Serbian (Latin)"; "sr-Latn-BA" = "Serbian (Latin, Bosnia and Herzegovina)"; "sr-Latn-ME" = "Serbian (Latin, Montenegro)"; "sr-Latn-RS" = "Serbian (Latin, Serbia)"; "sr-Latn-XK" = "Serbian (Latin, Kosovo)"; "ss" = "Swati"; "ss-SZ" = "Swati (Eswatini former Swaziland)"; "ss-ZA" = "Swati (South Africa)"; "ssy" = "Saho"; "ssy-ER" = "Saho (Eritrea)"; "st" = "Southern Sotho"; "st-LS" = "Sesotho (Lesotho)"; "st-ZA" = "Southern Sotho (South Africa)"; "sv" = "Swedish"; "sv-AX" = "Swedish (Åland Islands)"; "sv-FI" = "Swedish (Finland)"; "sv-SE" = "Swedish (Sweden)"; "sw" = "Kiswahili"; "sw-CD" = "Kiswahili (Congo DRC)"; "sw-KE" = "Kiswahili (Kenya)"; "sw-TZ" = "Kiswahili (Tanzania)"; "sw-UG" = "Kiswahili (Uganda)"; "syr" = "Syriac"; "syr-SY" = "Syriac (Syria)"; "ta" = "Tamil"; "ta-IN" = "Tamil (India)"; "ta-LK" = "Tamil (Sri Lanka)"; "ta-MY" = "Tamil (Malaysia)"; "ta-SG" = "Tamil (Singapore)"; "te" = "Telugu"; "te-IN" = "Telugu (India)"; "teo" = "Teso"; "teo-KE" = "Teso (Kenya)"; "teo-UG" = "Teso (Uganda)"; "tg" = "Tajik"; "tg-Cyrl" = "Tajik (Cyrillic)"; "tg-Cyrl-TJ" = "Tajik (Cyrillic, Tajikistan)"; "th" = "Thai"; "th-TH" = "Thai (Thailand)"; "ti" = "Tigrinya"; "ti-ER" = "Tigrinya (Eritrea)"; "ti-ET" = "Tigrinya (Ethiopia)"; "tig" = "Tigre"; "tig-ER" = "Tigre (Eritrea)"; "tk" = "Turkmen"; "tk-TM" = "Turkmen (Turkmenistan)"; "tn" = "Setswana"; "tn-BW" = "Setswana (Botswana)"; "tn-ZA" = "Setswana (South Africa)"; "to" = "Tongan"; "to-TO" = "Tongan (Tonga)"; "tr" = "Turkish"; "tr-CY" = "Turkish (Cyprus)"; "tr-TR" = "Turkish (Turkey)"; "ts" = "Tsonga"; "ts-ZA" = "Tsonga (South Africa)"; "tt" = "Tatar"; "tt-RU" = "Tatar (Russia)"; "twq" = "Tasawaq"; "twq-NE" = "Tasawaq (Niger)"; "tzm" = "Tamazight"; "tzm-Arab" = "Central Atlas Tamazight (Arabic)"; "tzm-Arab-MA" = "Central Atlas Tamazight (Arabic, Morocco)"; "tzm-Latn" = "Tamazight (Latin)"; "tzm-Latn-DZ" = "Tamazight (Latin, Algeria)"; "tzm-Latn-MA" = "Central Atlas Tamazight (Latin, Morocco)"; "tzm-Tfng" = "Tamazight (Tifinagh)"; "tzm-Tfng-MA" = "Central Atlas Tamazight (Tifinagh, Morocco)"; "ug" = "Uyghur"; "ug-CN" = "Uyghur (PRC)"; "uk" = "Ukrainian"; "uk-UA" = "Ukrainian (Ukraine)"; "ur" = "Urdu"; "ur-IN" = "Urdu (India)"; "ur-PK" = "Urdu (Islamic Republic of Pakistan)"; "uz" = "Uzbek"; "uz-Arab" = "Uzbek (Perso-Arabic)"; "uz-Arab-AF" = "Uzbek (Perso-Arabic, Afghanistan)"; "uz-Cyrl" = "Uzbek (Cyrillic)"; "uz-Cyrl-UZ" = "Uzbek (Cyrillic, Uzbekistan)"; "uz-Latn" = "Uzbek (Latin)"; "uz-Latn-UZ" = "Uzbek (Latin, Uzbekistan)"; "vai" = "Vai"; "vai-Latn" = "Vai (Latin)"; "vai-Latn-LR" = "Vai (Latin, Liberia)"; "vai-Vaii" = "Vai (Vai)"; "vai-Vaii-LR" = "Vai (Vai, Liberia)"; "ve" = "Venda"; "ve-ZA" = "Venda (South Africa)"; "vi" = "Vietnamese"; "vi-VN" = "Vietnamese (Vietnam)"; "vo" = "Volapük"; "vo-001" = "Volapük (World)"; "vun" = "Vunjo"; "vun-TZ" = "Vunjo (Tanzania)"; "wae" = "Walser"; "wae-CH" = "Walser (Switzerland)"; "wal" = "Wolaytta"; "wal-ET" = "Wolaytta (Ethiopia)"; "wo" = "Wolof"; "wo-SN" = "Wolof (Senegal)"; "xh" = "isiXhosa"; "xh-ZA" = "isiXhosa (South Africa)"; "xog" = "Soga"; "xog-UG" = "Soga (Uganda)"; "yav" = "Yangben"; "yav-CM" = "Yangben (Cameroon)"; "yi" = "Yiddish"; "yi-001" = "Yiddish (World)"; "yo" = "Yoruba"; "yo-BJ" = "Yoruba (Benin)"; "yo-NG" = "Yoruba (Nigeria)"; "zgh" = "Standard Moroccan Tamazight"; "zgh-Tfng" = "Standard Moroccan Tamazight (Tifinagh)"; "zgh-Tfng-MA" = "Standard Moroccan Tamazight (Tifinagh, Morocco)"; "zh" = "Chinese"; "zh-CN" = "Chinese (Simplified, PRC)"; "zh-Hans" = "Chinese (Simplified)"; "zh-Hans-HK" = "Chinese (Simplified Han, Hong Kong SAR)"; "zh-Hans-MO" = "Chinese (Simplified Han, Macao SAR)"; "zh-Hant" = "Chinese (Traditional)"; "zh-HK" = "Chinese (Traditional, Hong Kong S.A.R.)"; "zh-MO" = "Chinese (Traditional, Macao S.A.R.)"; "zh-SG" = "Chinese (Simplified, Singapore)"; "zh-TW" = "Chinese (Traditional, Taiwan)"; "zu" = "isiZulu"; "zu-ZA" = "isiZulu (South Africa)" } function info_locale { # Get the current user's language and region using the registry $Region = $localeLookup[(Get-ItemProperty -Path "HKCU:Control Panel\International\Geo").Nation] # Iterate through registry key in case multiple languages are configured (Get-ItemProperty -Path "HKCU:Control Panel\International\User Profile").Languages | ForEach-Object { $Languages += " - $($languageLookup[$_])" } return @{ title = "Locale" content = "$Region$Languages" } } function info_region { $Region = $localeLookup[(Get-ItemProperty -Path "HKCU:Control Panel\International\Geo").Nation] return @{ title = "Locale" content = "$Region" } } # Retrieve the primary language (first language configured) function info_language { $PrimaryLanguage = (Get-ItemProperty -Path "HKCU:Control Panel\International\User Profile").Languages[0] $content = $($languageLookup[$PrimaryLanguage]) return @{ title = "Language" content = "$content" } } function info_mini_language { $PrimaryLanguage = (Get-ItemProperty -Path "HKCU:Control Panel\International\User Profile").Languages[0] $content = $($languageLookup[$PrimaryLanguage]) # Remove any part after and including the bracket to fit it in double column layout $content = $content -replace "\s*\(.*\)", "" return @{ title = "Language" content = "$content" } } # ===== WEATHER ===== function info_weather { $currentTempC = $weatherInfoParts[1].TrimStart("+") $weatherCondition = ($weatherInfoParts[7..($weatherInfoParts.Length - 1)] -join " ").Trim() $feelsLikeTempC = $weatherInfoParts[2].TrimStart("+") $wind = $weatherInfoParts[4].Trim() # Check if any of the components contain "Network Error" if ($currentTempC -like "*Network Error*" -or $weatherCondition -like "*Network Error*" -or $feelsLikeTempC -like "*Network Error*" -or $wind -like "*Network Error*") { return @{ title = "Weather" content = "Network Error" } } return @{ title = "Weather" content = "$currentTempC - $weatherCondition (Feels Like: $feelsLikeTempC) $wind" } } function info_weather_condition { $weatherCondition = ($weatherInfoParts[7..($weatherInfoParts.Length - 1)] -join " ").Trim() if ($weatherCondition -like "*Network Error*") { $weatherCondition = "Network Error" } return @{ title = "Weather" content = $weatherCondition } } function info_humidity { $humidity = $weatherInfoParts[3] return @{ title = "Humidity" content = "$humidity" } } function info_sun { $sunrise = $weatherInfoParts[5] $sunset = $weatherInfoParts[6] if ($sunrise -eq "Network Error" -or $sunset -eq "Network Error") { return @{ title = "Sunrise" content = "Network Error" } } $today = Get-Date -Format "yyyy-MM-dd" $sunriseDateTime = [DateTime]::ParseExact("$today $sunrise", "yyyy-MM-dd HH:mm:ss", $null) $sunsetDateTime = [DateTime]::ParseExact("$today $sunset", "yyyy-MM-dd HH:mm:ss", $null) $currentTime = Get-Date # Determine whether it's daytime or nighttime if ($currentTime -ge $sunriseDateTime -and $currentTime -lt $sunsetDateTime) { # Daytime: Show sunset time $timeToShow = $sunsetDateTime.ToString("hh:mm tt") $timeLabel = "Sunset" } else { # Nighttime: Show sunrise time $timeToShow = $sunriseDateTime.ToString("hh:mm tt") $timeLabel = "Sunrise" } return @{ title = $timeLabel content = "$timeToShow" } } function info_temp_celcius { $currentTempC = $weatherInfoParts[1] return @{ title = "Temperature" content = "$currentTempC" } } function info_temp_farenheit { $currentTempC = $weatherInfoParts[1] if ($currentTempC -ne "Network Error") { $currentTempF = "{0:N1}" -f (([double]($currentTempC -replace '[^0-9.-]') * 9 / 5) + 32) } return @{ title = "Temperature" content = "$currentTempF°F" } } function info_wind { $wind = $weatherInfoParts[4] return @{ title = "Wind" content = $wind } } function info_feels_like_celcius { $feelsLikeTempC = $weatherInfoParts[2] return @{ title = "Feels Like" content = $feelsLikeTempC } } function info_feels_like_farenheit { $feelsLikeTempC = $weatherInfoParts[2] if ($feelsLikeTempC -ne "Network Error") { $feelsLikeTempF = "{0:N1}" -f (([double]($feelsLikeTempC -replace '[^0-9.-]') * 9 / 5) + 32) } return @{ title = "Feels Like" content = "$feelsLikeTempF°F" } } # ===== IP ===== function info_local_ip { try { # Get all network adapters foreach ($ni in [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()) { # Get the IP information of each adapter $properties = $ni.GetIPProperties() # Check if the adapter is online, has a gateway address, and the adapter does not have a loopback address if ($ni.OperationalStatus -eq 'Up' -and !($null -eq $properties.GatewayAddresses[0]) -and !$properties.GatewayAddresses[0].Address.ToString().Equals("0.0.0.0")) { # Check if adapter is a WiFi or Ethernet adapter if ($ni.NetworkInterfaceType -eq "Wireless80211" -or $ni.NetworkInterfaceType -eq "Ethernet") { foreach ($ip in $properties.UnicastAddresses) { if ($ip.Address.AddressFamily -eq "InterNetwork") { if (!$local_ip) { $local_ip = $ip.Address.ToString() } } } } } } } catch { } return @{ title = "Local IP" content = if (-not $local_ip) { "$e[91m(Unknown)" } else { $local_ip } } } function info_public_ip { return @{ title = "Public IP" content = try { Invoke-RestMethod -TimeoutSec 5 ifconfig.me/ip } catch { "$e[91m(Network Error)" } } } function info_gradient { if ($stripansi) { return @{ title = "" content = "" } } $e = [char]27 $topColors = @(167, 173, 179, 185, 149, 113, 77, 80, 74, 68, 62, 98, 134, 170, 169, 168) $bottomColors = @(160, 202, 214, 226, 154, 82, 47, 51, 39, 33, 57, 93, 129, 165, 201, 199) $topContent = ($topColors | ForEach-Object { "{0}[0;48;5;{1}m{2}" -f $e, $_, ' ' }) -join '' $bottomContent = ($bottomColors | ForEach-Object { "{0}[0;48;5;{1}m{2}" -f $e, $_, ' ' }) -join '' return @( @{ title = "" content = "$e[1;33m$($topContent)$e[0m" }, @{ title = "" content = "$e[1;33m$($bottomContent)$e[0m" } ) } function info_colorbar { # If stripansi, avoid showing the empty space taken up by colorbar if ($stripansi) { return @{ title = "" content = "" } } return @( @{ title = "" content = ('{0}[0;40m{1}{0}[0;41m{1}{0}[0;42m{1}{0}[0;43m{1}{0}[0;44m{1}{0}[0;45m{1}{0}[0;46m{1}{0}[0;47m{1}{0}[0m') -f $e, ' ' }, @{ title = "" content = ('{0}[0;100m{1}{0}[0;101m{1}{0}[0;102m{1}{0}[0;103m{1}{0}[0;104m{1}{0}[0;105m{1}{0}[0;106m{1}{0}[0;107m{1}{0}[0m') -f $e, ' ' } ) } function info_colorbar_center { info_colorbar } function info_gradient_center { info_gradient } # Center the logo when bottom_max_length > logo_max_length function pad_line_center { param ( [string]$line, [int]$targetLength ) $plain_line = $line -replace $ansiRegex, "" $line_length = $plain_line.Length $padding_needed = $targetLength - $line_length if ($padding_needed -le 0) { return $line } $left_padding = " " * [Math]::Floor($padding_needed / 2) return "$left_padding$line" } # Determine the longest line in the logo $logo_max_length = 0 if ($img) { for ($i = 0; $i -lt $img.Count; $i++) { $plain_line = $img[$i] -replace $ansiRegex, "" if ($plain_line.Length -gt $logo_max_length) { $logo_max_length = $plain_line.Length } $img[$i] = " $($img[$i])" # Add space to the left } } # ===== Text + Bar Alignment Helper Functions ===== function CalculateTextLengths { param ( [array]$Lines, [string]$Pattern ) $textLengths = foreach ($line in $Lines) { $cleanContent = $line.Content -replace $ansiRegex, "" if ($cleanContent -match $Pattern) { $matches[1].Length } else { $cleanContent.Length } } return $textLengths } function AlignLineContent { param ( [string]$OriginalContent, [string]$Pattern, [int]$MaxTextLength, [bool]$IsTextbar, [int]$GroupCount ) $cleanContent = $OriginalContent -replace $ansiRegex, "" if ($cleanContent -match $Pattern) { $textPart = $matches[1] if ($IsTextbar) { $spacesToAdd = $GroupCount -gt 1 ? $MaxTextLength + 1 - $textPart.Length : $MaxTextLength - $textPart.Length } else { $spacesToAdd = $MaxTextLength - $textPart.Length } $spacing = ' ' * $spacesToAdd # Find insert position preserving ANSI codes $insertPosition = 0 $visibleChars = 0 while ($visibleChars -lt $matches[1].Length -and $insertPosition -lt $OriginalContent.Length) { if ($OriginalContent[$insertPosition] -eq "`e") { # Skip ANSI codes $ansiEnd = $OriginalContent.IndexOf("m", $insertPosition) if ($ansiEnd -ge 0) { $insertPosition = $ansiEnd + 1 } else { break } } else { $insertPosition += 1 $visibleChars += 1 } } # Insert spacing return $OriginalContent.Insert($insertPosition, $spacing) } else { return $OriginalContent } } # Used when only config_left function AlignAndOutputLines { param ( [array]$Lines, [string]$Pattern, [switch]$IsTextbar ) # Calculate text lengths $textLengths = CalculateTextLengths -Lines $Lines -Pattern $Pattern $maxTextLength = ($textLengths | Measure-Object -Maximum).Maximum # Iterate through each line to align foreach ($line in $Lines) { $newContent = AlignLineContent -OriginalContent $line.Content -Pattern $Pattern -MaxTextLength $maxTextLength -IsTextbar $IsTextbar -GroupCount $Lines.Count if ($stripansi) { $output = $newContent -replace $ansiRegex, "" if ($output.Length -gt $freeSpace) { $output = $output.Substring(0, $freeSpace) } } else { $output = truncate_line $newContent $freeSpace } Write-Output $output } } # Used in ProcessConfigItems function AlignLinesInGroup { param ( [array]$Group, [string]$Pattern, [bool]$IsTextbar ) $textLengths = CalculateTextLengths -Lines $Group -Pattern $Pattern $maxTextLength = ($textLengths | Measure-Object -Maximum).Maximum foreach ($line in $Group) { $line.Content = AlignLineContent -OriginalContent $line.Content -Pattern $Pattern -MaxTextLength $maxTextLength -IsTextbar $IsTextbar -GroupCount $Group.Count } } function ProcessHeaderItems { param ( [array]$headerItems, [ref]$outputLines, [ref]$maxLength, [hashtable]$colorMap ) # Default color to apply to all elements $defaultColor = Get-AnsiCode -colorInput "white" foreach ($itemArray in $headerItems) { $lineContents = @() foreach ($item in $itemArray) { $functionToCall = "info_$item" if (Get-Command -Name $functionToCall -ErrorAction SilentlyContinue) { $info = & $functionToCall } else { $info = @{ content = "`e[31mFunction '$functionToCall' not found`e[0m" } } if ($info -is [array]) { # If the info is an array, take the first element $info = $info[0] } if ($info["content"]) { $content = $info["content"] # Determine the color for this item $color = if ($colorMap.ContainsKey($item)) { Get-AnsiCode -colorInput $colorMap[$item] } else { $defaultColor } if ($color) { $content = "${ansiReset}`e[${color}m$content`e[0m" } } else { $content = "" } $lineContents += $content } $lineContent = $lineContents -join " " # Remove ANSI codes to compute plain line length $plain_line = $lineContent -replace $ansiRegex, "" if ($plain_line.Length -gt $maxLength.Value) { $maxLength.Value = $plain_line.Length } # Append the processed line to output $outputLines.Value += $lineContent } # Remove any trailing empty lines if ($outputLines.Value[-1] -eq "") { $outputLines.Value = $outputLines.Value[0..($outputLines.Value.Count - 2)] } } $isRightColumn = $false $isBottomSection = $false function ColorOutput { param ( [string]$Title, [string]$Content, [string]$ColorKey, [string]$ColorValue ) # Detect if line["content"] has altbar + text # If found, determine the starting index of the text after bar and apply color codes from that position onward. $pattern = '^(▇+)\s*(.+)$' $ansiPattern = "`e\[[0-9;]*m" # Construct colored title if it exists $coloredTitle = if ($Title) { "`e[1;${ColorKey}m${Title}" } else { "" } # Construct colored content if it exists $coloredContent = if ($Content) { $modContent = $Content -replace $ansiPattern, "" if ($modContent -match $pattern) { $bar = $matches[1] $text = $matches[2] # Initialize counters to find the insertion point in the original content $originalIndex = 0 $cleanIndex = 0 # Find the position in originalContent after the bar while ($cleanIndex -lt $bar.Length -and $originalIndex -lt $Content.Length) { if ($Content[$originalIndex] -eq "`e") { # Detected start of ANSI code $ansiEnd = $Content.IndexOf("m", $originalIndex) if ($ansiEnd -ge 0) { # Skip the entire ANSI code $originalIndex = $ansiEnd + 1 } else { # Malformed ANSI code, break break } } else { # Regular character $originalIndex += 1 $cleanIndex += 1 } } # Prepare the color codes $colorStart = "`e[1;${ColorValue}m" $colorEnd = "`e[0m" $spaceIndex = $originalIndex while ($spaceIndex -lt $Content.Length -and $Content[$spaceIndex] -eq ' ') { $spaceIndex += 1 } # Insert the color start code before the text $newContent = $Content.Insert($spaceIndex, $colorStart) # Insert the reset code after the text $textEndIndex = $spaceIndex + $colorStart.Length + $text.Length $newContent = $newContent.Insert($textEndIndex, $colorEnd) # Assign the modified content $newContent } else { # If pattern does not match, apply color to the entire content "`e[1;${ColorValue}m${Content}`e[0m" } } else { "" } # Combine the parts based on their existence if ($coloredTitle -and $coloredContent) { "${ansiReset}${coloredTitle}: ${coloredContent}" } elseif ($coloredTitle) { "$coloredTitle" } else { "$coloredContent" } } $isGeneralItem = $false function ProcessConfigItems { param ( [array]$configItems, [ref]$outputLines, [ref]$maxLength ) switch ($true) { $isBottomSection { $colorKey = Get-AnsiCode -colorInput $colorBottomKey $colorValue = Get-AnsiCode -colorInput $colorBottomValue break } $isRightColumn { $colorKey = Get-AnsiCode -colorInput $colorRightKey $colorValue = Get-AnsiCode -colorInput $colorRightValue break } default { $colorKey = Get-AnsiCode -colorInput $colorLeftKey $colorValue = Get-AnsiCode -colorInput $colorLeftValue } } # Pre-fetch all available info_* functions $availableInfoFunctions = @{} Get-Command info_* -CommandType Function -ErrorAction SilentlyContinue | ForEach-Object { $availableInfoFunctions[$_.Name] = $_ } # Initialize a list to store deferred items (colorbar and colorbar_center) # colorbar_center should be deferred until the maximum length of the section is determined. # To find the maximum length, all elements need to be processed first. $deferredItems = @() $lineIndex = 0 $ansiPattern = "\e\[[0-9;]*m" foreach ($item in $configItems) { # Determine if the item has the 'general_' prefix $isGeneralItem = $false if ($item.StartsWith('general_')) { $isGeneralItem = $true $itemName = $item.Substring(8) # Remove 'general_' prefix $functionToCall = "info_$itemName" if (-not [string]::IsNullOrWhiteSpace($colorGeneralKey)) { $currentColorKey = Get-AnsiCode -colorInput $colorGeneralKey } else { $currentColorKey = $colorKey } if (-not [string]::IsNullOrWhiteSpace($colorGeneralValue)) { $currentColorValue = Get-AnsiCode -colorInput $colorGeneralValue } else { $currentColorValue = $colorValue } } else { $functionToCall = "info_$item" $currentColorKey = $colorKey $currentColorValue = $colorValue } # Retrieve the corresponding info function if it exists, else create an error message if ($availableInfoFunctions.ContainsKey($functionToCall)) { $info = & $functionToCall } else { $info = @{ title = "`e[31mFunction '$functionToCall' not found" } } # Skip processing if $info is null or empty if (-not $info) { $lineIndex++ continue } if ($info -isnot [array]) { $info = @($info) } # Process each line in the info object foreach ($line in $info) { if ($item -in @("colorbar_center", "gradient_center")) { # Defer processing for colorbar items $deferredItems += [PSCustomObject]@{ LineIndex = $lineIndex LineData = $line IsBottomSection = $isBottomSection ConfigItem = $item IsGeneralItem = $isGeneralItem } # Placeholder in outputLines $outputLines.Value += $null } else { # Apply ANSI colors to title and content $title = $line["title"] $content = $line["content"] $output = ColorOutput -Title $title -Content $content -ColorKey $currentColorKey -ColorValue $currentColorValue # Calculate the plain text length by removing ANSI codes $plainOutputLength = ($output -replace $ansiPattern, "").Length # Update maxLength if necessary (excluding general items) if (-not $isGeneralItem -and $plainOutputLength -gt $maxLength.Value) { $maxLength.Value = $plainOutputLength } # Append the processed line to outputLines $outputLines.Value += [PSCustomObject]@{ Content = $output ConfigItem = $item IsGeneralItem = $isGeneralItem } } $lineIndex++ } } # Detects groups of text + bar or bar + text based on the pattern passed in function DetectPatternGroups { param ( [array]$Lines, [string]$Pattern ) $groups = @() $currentGroup = @() $AnsiPattern = "\e\[[0-9;]*m" foreach ($line in $Lines) { $cleanContent = $line.Content -replace $AnsiPattern, "" # If content matches pattern, add to current group if ($cleanContent -match $Pattern) { $currentGroup += $line } else { # End of matching implies one group has ended, and so add currentGroup to groups # Reset currentGroup and and gt -0 condition to prevent unmatched patterns being added to currentGroup if ($currentGroup.Count -gt 0) { $groups += , @($currentGroup) $currentGroup = @() } } } # In case a section only contains of bars if ($currentGroup.Count -gt 0) { $groups += , @($currentGroup) } # Append @(), because otherwise, if a section has only one group with 3 bars, it will be interpreted # as 3 groups, each with one bar $groups += , @() return $groups } $ansiPattern = "\e\[[0-9;]*m" # Detect and align bartext groups $bartextPattern = '^(.*?)(▇+|\[[\s■\-]+\])\s+(.+)$' $bartextGroups = DetectPatternGroups -Lines $outputLines.Value -Pattern $bartextPattern foreach ($group in $bartextGroups) { AlignLinesInGroup -Group $group -Pattern $bartextPattern -IsTextbar:$false } # Detect and align textbar groups $textbarPattern = '^(?!\s*$).+(?:▇+$|\[[\s■\-]+\])$' $textbarGroups = DetectPatternGroups -Lines $outputLines.Value -Pattern $textbarPattern $textbarPattern = '^(.*?)(▇+|\[[\s■\-]+\])$' foreach ($group in $textbarGroups) { AlignLinesInGroup -Group $group -Pattern $textbarPattern -IsTextbar:$true } # Process deferred colorbar and colorbar_center items foreach ($deferredItem in $deferredItems) { $line = $deferredItem.LineData $index = $deferredItem.LineIndex $item = $deferredItem.ConfigItem $isGeneralItem = $deferredItem.IsGeneralItem $outputLine = $line["content"] # Determine target length for centering if ($deferredItem.IsBottomSection) { $targetLength = [Math]::Max($maxLength.Value, $logo_max_length) } else { $targetLength = $maxLength.Value } # Center the output line using the pad_line_center function $centeredLine = pad_line_center $outputLine $targetLength # Create the output object $outputObject = [PSCustomObject]@{ Content = $centeredLine ConfigItem = $item IsGeneralItem = $isGeneralItem } # Insert the centered line into outputLines at the correct index $outputLines.Value[$index] = $outputObject } } # Returns true if config has items other than 'blank' function check_non_blank { param ( [string[]]$ConfigArray ) $elements = @('blank', 'colorbar', 'colorbar_center', 'gradient', 'gradient_center') if ($stripansi) { foreach ($item in $ConfigArray) { if (-not $($elements -contains $item.Trim().ToLower())) { return $true } } } else { foreach ($item in $ConfigArray) { if ($item.Trim().ToLower() -ne 'blank') { return $true } } } return $false } # ===== COLLECT WEATHER DATA ===== $all_config = ($config_left + $config_right + $config_bottom | Select-Object -Unique) $weather_functions = @( "weather", "humidity", "sun", "temp_celcius", "temp_farenheit", "wind", "feels_like_celcius", "feels_like_farenheit" ) function get_weather_data { $url = "http://wttr.in?format=%c+%t+%f+%h+%w+%S+%s+%C&m" try { $response = Invoke-RestMethod -TimeoutSec 3 -Uri $url return $response -split "\s+" } catch { $output = @() for ($i = 0; $i -lt $weather_functions.Length; $i++) { $output += "Network Error" } return $output } } $contains_weather_function = $weather_functions | ForEach-Object { if ($all_config -contains $_) { return $true } } if ($contains_weather_function) { $weatherInfoParts = get_weather_data } if (-not $stripansi) { # Unhide the cursor after a terminating error trap { "$e[?25h"; break } # Reset terminal sequences and display a newline Write-Output "$e[0m$e[?25l" } else { Write-Output "" } $config_bottom_not_blank = check_non_blank -ConfigArray $config_bottom if (!$config_bottom_not_blank -and -not $stripansi ) { foreach ($line in $img) { Write-Output $line } } $bottom_output_lines = @() $bottom_max_length = 0 $isRightColumn = $false $isBottomSection = $true if ($config_bottom_not_blank) { ProcessConfigItems -configItems $config_bottom -outputLines ([ref]$bottom_output_lines) -maxLength ([ref]$bottom_max_length) } for ($i = 0; $i -lt $bottom_output_lines.Count; $i++) { $bottom_output_lines[$i].Content = " " + $bottom_output_lines[$i].Content } # Add 1 to account for the space in the front $bottom_max_length += 1 $max_logo_bottom_length = [Math]::Max($logo_max_length, $bottom_max_length) $bottom_gt_logo = $bottom_max_length -gt $logo_max_length # The "Bottom not blank" condition prevents double printing of the logo. # If $bottom_gt_logo is true, it indicates that the logo needs to be processed for centering. # When the number of bottom output lines is too long and the terminal height is insufficient, # the cursor movement technique may not work correctly. # If the terminal window cannot display all lines at once, it scrolls. # As a result, moving the cursor up by a relative number of lines will not return it to the top of the logo. # To manage this situation, we concatenate different sections and print all the information together at once. # Skip the printing process if stripansi is false because cursor movment not possible witout ANSI escape codes if ($(($config_bottom_not_blank -and -not $bottom_gt_logo ) -and ( $bottom_output_lines.Length -lt 9 ) -and -not $stripansi)) { foreach ($line in $img) { Write-Output $line } foreach ($line in $bottom_output_lines) { $lineContent = truncate_line $line.Content $max_logo_bottom_length Write-Output "$lineContent" } } # $GAP = 3 # $writtenLines = 0 # Process Header Items $header_output_lines = @() $footer_output_lines = @() $header_max_length = 0 $footer_max_length = 0 ProcessHeaderItems -headerItems $header -outputLines ([ref]$header_output_lines) -maxLength ([ref]$header_max_length) -colorMap $headerColorMap ProcessHeaderItems -headerItems $footer -outputLines ([ref]$footer_output_lines) -maxLength ([ref]$footer_max_length) -colorMap $footerColorMap $config_right_blank = -not $(check_non_blank -ConfigArray $config_right) $center_functions = @("colorbar_center", "gradient_center") $not_contain_center = ($center_functions | ForEach-Object { $_ -notin $config_left }) -notcontains $false $freeSpace = 0 function AlignBartextLines { param ( [array]$BartextLines, [string]$Pattern = '^(.*?)(▇+|\[[\s■\-]+\])\s+(.+)$' ) AlignAndOutputLines -Lines $BartextLines -Pattern $Pattern -IsTextbar:$false } function AlignTextbarLines { param ( [array]$TextbarLines, [string]$Pattern = '^(.*?)(▇+|\[[\s■\-]+\])$' ) AlignAndOutputLines -Lines $TextbarLines -Pattern $Pattern -IsTextbar:$true } if ($($config_right_blank -and $not_contain_center) -and -not $bottom_gt_logo -and $bottom_output_lines.Length -lt 9) { $GAP = 3 $writtenLines = 0 $freeSpace = $Host.UI.RawUI.WindowSize.Width - 1 # move cursor to top of image and to its right if ($img -and -not $stripansi) { $bottomSectionLines = $bottom_output_lines.Count $logoHeight = $img.Length $moveUpLines = $logoHeight + $bottomSectionLines + 1 $freeSpace -= 1 + $COLUMNS + $GAP Write-Output "$e[${moveUpLines}A" } $colorKey = Get-AnsiCode -colorInput $colorLeftKey $colorValue = Get-AnsiCode -colorInput $colorLeftValue Start-Sleep -Milliseconds 1 for ($i = 0; $i -lt $header_output_lines.Count; $i++) { $output = $header_output_lines[$i] if ($img) { if (-not $stripansi) { # move cursor to right of image $output = "$e[$(2 + $COLUMNS + $GAP)G$output" } else { # write image progressively $imgline = ("$($img[$writtenLines])" -replace $ansiRegex, "").PadRight($COLUMNS) $output = " $imgline $output" } } $writtenLines++ if ($stripansi) { $output = $output -replace $ansiRegex, "" if ($output.Length -gt $freeSpace) { $output = $output.Substring(0, $freeSpace) } } else { $output = truncate_line $output $freeSpace } Write-Output $output } # Initialize variables $bufferedBartextLines = @() $bufferedTextbarLines = @() $bartextPattern = '^(.*?)(▇+|\[[\s■\-]+\])\s+(.+)$' $textbarPattern = '^(?!\s*$).+(?:▇+$|\[[\s■\-]+\])$' foreach ($item in $config_left) { $isRightColumn = $false $isBottomSection = $false $isGeneralItem = $false if ($item.StartsWith('general_')) { $isGeneralItem = $true $itemName = $item.Substring(8) # Remove 'general_' prefix $functionToCall = "info_$itemName" } else { $functionToCall = "info_$item" } # Check if the function exists before calling if (Get-Command -Name $functionToCall -ErrorAction SilentlyContinue) { $info = & $functionToCall } else { $info = @{ title = "`e[31mFunction '$functionToCall' not found" } } if (-not $info) { continue } if ($info -isnot [array]) { $info = @($info) } foreach ($line in $info) { $title = $line["title"] $content = $line["content"] $output = ColorOutput -Title $title -Content $content -ColorKey $colorKey -ColorValue $colorValue if ($img) { if (-not $stripansi) { # Move cursor to the right of the image $output = "$e[$(2 + $COLUMNS + $GAP)G$output" } else { # Write image progressively # Added +2 to fix the indentation issue when there is no image $imgline = ("$($img[$writtenLines])" -replace $ansiRegex, "").PadRight($COLUMNS + 2) $output = " $imgline $output" } } $writtenLines++ if ($stripansi) { $output = $output -replace $ansiRegex, "" if ($output.Length -gt $freeSpace) { $output = $output.Substring(0, $freeSpace) } } else { $output = truncate_line $output $($freeSpace) } # Determine if the current line matches the bartext or textbar pattern # If yes, hold on printing until the streak ends and then output them together aligned $cleanOutput = $output -replace $ansiRegex, "" $isBartext = $cleanOutput -match $bartextPattern $isTextbar = $cleanOutput -match $textbarPattern if ($isBartext) { # Add the current line to the buffer $bufferedBartextLines += @{ Content = $output } continue } if ($isTextbar) { $bufferedTextbarLines += @{ Content = $output } continue } else { # If there are buffered bartext lines, process and output them first if ($bufferedBartextLines.Count -gt 0) { AlignBartextLines -BartextLines $bufferedBartextLines $bufferedBartextLines = @() } if ($bufferedTextbarLines.Count -gt 0) { AlignTextbarLines -TextbarLines $bufferedTextbarLines $bufferedTextbarLines = @() } Write-Output $output } } } # After processing all items, check if there are any remaining buffered bartext or textbar lines if ($bufferedBartextLines.Count -gt 0) { AlignBartextLines -BartextLines $bufferedBartextLines $bufferedBartextLines = @() } if ($bufferedTextbarLines.Count -gt 0) { AlignTextbarLines -TextbarLines $bufferedTextbarLines $bufferedTextbarLines = @() } Start-Sleep -Milliseconds 1 for ($i = 0; $i -lt $footer_output_lines.Count; $i++) { $output = $footer_output_lines[$i] if ($img) { if (-not $stripansi) { # move cursor to right of image $output = "$e[$(2 + $COLUMNS + $GAP)G$output" } else { # write image progressively $imgline = ("$($img[$writtenLines])" -replace $ansiRegex, "").PadRight($COLUMNS) if ($writtenLines -lt $img.Length) { $output = " ${imgline} ${output}" } else { $output = " ${imgline} ${output}" } } } $writtenLines++ if ($stripansi) { $output = $output -replace $ansiRegex, "" if ($output.Length -gt $freeSpace) { $output = $output.Substring(0, $freeSpace) } } else { $output = truncate_line $output $($freeSpace) } Write-Output $output } if ($stripansi) { # write out remaining image lines for ($i = $writtenLines; $i -lt $img.Length; $i++) { $imgline = ("$($img[$i])" -replace $ansiRegex, "").PadRight($COLUMNS) Write-Output " $imgline" } } # move cursor back to the bottom and print 2 newlines if (-not $stripansi) { Write-Output "$e[?25h" } else { Write-Output "`n" } } else { $left_output_lines = @() $right_output_lines = @() $left_max_length = 0 $right_max_length = 0 $isRightColumn = $false $isBottomSection = $false if (check_non_blank -ConfigArray $config_left) { ProcessConfigItems -configItems $config_left -outputLines ([ref]$left_output_lines) -maxLength ([ref]$left_max_length) } $isRightColumn = $true $isBottomSection = $false if (check_non_blank -ConfigArray $config_right) { ProcessConfigItems -configItems $config_right -outputLines ([ref]$right_output_lines) -maxLength ([ref]$right_max_length) } $total_length = $max_logo_bottom_length + 6 + [Math]::Max([Math]::Max($right_max_length + $left_max_length, $header_max_length), $footer_max_length) $cutoff = $false if ($total_length -gt $Host.UI.RawUI.WindowSize.Width -and $bottom_gt_logo) { $indentation_length = $logo_max_length + 4 $cutoff = $true $max_logo_bottom_length = $logo_max_length } else { $indentation_length = $max_logo_bottom_length + 4 # Add 4 spaces if ($stripansi) { $indentation_length = $max_logo_bottom_length + 3 } } $freeSpace = $Host.UI.RawUI.WindowSize.Width - $indentation_length - 1 $indentation = " " * $indentation_length $max_lines = [Math]::Max($left_output_lines.Count, $right_output_lines.Count) $combinedLines = @() # Define the escape character $e = [char]27 $colorbarPattern = "^\s{2,}(?:$e\[[0-9;]+m\s*)+" if($cutoff -and -not $stripansi) { foreach ($line in $bottom_output_lines) { if ($line.Content -match $colorbarPattern) { $bar_length = $($line.Content -replace $ansiRegex, "") $line.Content = $($line.Content).TrimStart() if($bar_length -lt $logo_max_length) { $line.Content = pad_line_center $line.Content $logo_max_length } $line.Content = " " + $line.Content } } } # Center the logo if $centerlogo is true and image exists # If cutoff is true, the max length of bottom section is restricted by logo_max_length. # In consequence, if cutoff is true, centering the logo is not necessary. if ($img -and $centerlogo -and (-not $cutoff -or $bottom_max_length -le $logo_max_length)) { for ($i = 0; $i -lt $img.Count; $i++) { $img[$i] = pad_line_center $img[$i] ($max_logo_bottom_length + 1) # +1 for the added space to the left } } for ($i = 0; $i -lt $max_lines; $i++) { $left_line = if ($i -lt $left_output_lines.Count) { $left_output_lines[$i].Content } else { "" } $left_config_item = if ($i -lt $left_output_lines.Count) { $left_output_lines[$i].ConfigItem } else { "" } $left_is_general = if ($i -lt $left_output_lines.Count) { $left_output_lines[$i].IsGeneralItem } else { $false } $right_line = if ($i -lt $right_output_lines.Count) { $right_output_lines[$i].Content } else { "" } $right_config_item = if ($i -lt $right_output_lines.Count) { $right_output_lines[$i].ConfigItem } else { "" } # Remove extra space in the first row except colorbar_center or gradient_center if ($left_config_item -ne 'colorbar_center' -or $left_config_item -ne 'gradient_center') { $left_line = $left_line.TrimStart() } if ($right_config_item -ne 'colorbar_center' -or $right_config_item -ne 'gradient_center') { $right_line = $right_line.TrimStart() } # Calculate spaces needed $plain_left_line = $left_line -replace $ansiRegex, "" if ($right_line -eq "" -Or $null -eq $right_line) { $spaces_needed = $left_max_length - $plain_left_line.Length } else { $spaces_needed = $left_max_length - $plain_left_line.Length + 3 } if ($spaces_needed -lt 0) { $spaces_needed = 0 } $spaces = " " * $spaces_needed if ($img) { if (-not $stripansi) { # Move cursor to the indentation position $left_line = "$e[${indentation_length}G$left_line" } else { $index = $i + $header_output_lines.Length if ($index -lt $img.Length) { if ($cutoff) { $left_line = " $left_line" } else { $mod_img = $img[$i] -replace $ansiRegex, "" if ($bottom_max_length -gt $mod_img.Length) { $space_count = " " * $($bottom_max_length - $mod_img.Length + 3) } else { $space_count = " " * 3 } $left_line = "$space_count$left_line" } } else { if (!$config_bottom_not_blank) { $left_line = " $left_line" } else { $index = $index - $img.Length $current_text = $bottom_output_lines[$index].Content -replace $ansiRegex, "" if ($cutoff) { if ($current_text.Length -gt $logo_max_length) { $space_count = " " * 3 } else { $space_count = " " * $($logo_max_length - $current_text.Length + 3) } } else { $space_count = " " * $($max_logo_bottom_length - $current_text.Length + 3) } $left_line = "$space_count$left_line" } } } } else { # If no image, no indentation $left_line = "$left_line" } # If config_left has general flag, skip corresponding right item if ($left_is_general) { $combined_line = "$left_line" } else { $combined_line = "$left_line$spaces$right_line" } $combinedLines += $combined_line } # Handle header and footer spacing, specifically in stripansi mode function HeaderFooterSpacing { param ( [string]$line, [int]$index ) if ($img) { if (-not $stripansi) { # Move cursor to the indentation position $line = "$e[${indentation_length}G$line" } else { if ($index -lt $img.Length) { if ($cutoff) { $line = " $line" } else { $mod_img = $img[$index] -replace $ansiRegex, "" if ($bottom_max_length -gt $mod_img.Length) { $space_count = " " * ($bottom_max_length - $mod_img.Length + 3) } else { $space_count = " " * 3 } $line = "$space_count$line" } } else { if (-not $config_bottom_not_blank) { $line = " $line" } else { $index = $index - $img.Length $current_text = $bottom_output_lines[$index].Content -replace $ansiRegex, "" if ($cutoff) { if ($current_text.Length -gt $logo_max_length) { $space_count = " " * 3 } else { $space_count = " " * ($logo_max_length - $current_text.Length + 3) } } else { $space_count = " " * ($max_logo_bottom_length - $current_text.Length + 3) } $line = "$space_count$line" } } } } return $line } for ($i = 0; $i -lt $header_output_lines.Count; $i++) { $line = $header_output_lines[$i] $header_output_lines[$i] = HeaderFooterSpacing $line $i } for ($i = 0; $i -lt $footer_output_lines.Count; $i++) { $line = $footer_output_lines[$i] $index = $i + $header_output_lines.Length + $combinedLines.Length $footer_output_lines[$i] = HeaderFooterSpacing $line $index } $combinedLines = $header_output_lines + $combinedLines + $footer_output_lines if ($bottom_output_lines.Length -lt 9 -and $logo_max_length -ge $bottom_max_length -and -not $stripansi) { if ($img) { $bottomSectionLines = $bottom_output_lines.Count $logoHeight = $img.Length $moveUpLines = $logoHeight + $bottomSectionLines + 1 Write-Output "$e[${moveUpLines}A" } foreach ($combined_line in $combinedLines) { $lineContent = "" $lineContent = truncate_line $combined_line $freeSpace Write-Output $lineContent $writtenLines++ } # $max_lines = [Math]::Max($img.Count + $bottom_output_lines.Count, $combinedLines.Count) if($($img.Count + $bottom_output_lines.Count) -gt $combinedLines.Count) { for ($i = 0; $i -lt ($bottom_output_lines.Count - 2); $i++) { Write-Output "" } } } else { # Determine the maximum number of lines needed $max_lines = [Math]::Max($img.Count + $bottom_output_lines.Count, $combinedLines.Count) # Index for bottom_output_lines $bottomIndex = 0 for ($i = 0; $i -lt $max_lines; $i++) { $imgLine = "" $bottomLine = "" $combinedLine = "" # Get the current line from $img if within range, otherwise $bottom_output_lines if ($i -lt $img.Count) { $imgLine = $img[$i] } elseif ($bottomIndex -lt $bottom_output_lines.Count) { $bottomLine = $bottom_output_lines[$bottomIndex].Content $bottomIndex++ } # Get the current combined line if within range if ($i -lt $combinedLines.Count) { $combinedLine = $combinedLines[$i] } if ($imgLine) { $processedLine1 = truncate_line $imgLine $max_logo_bottom_length } elseif ($bottomLine) { $processedLine1 = truncate_line $bottomLine $($max_logo_bottom_length) } else { $processedLine1 = "" if ($stripansi) { $processedLine1 = " " * $max_logo_bottom_length } } if ($combinedLine) { $processedLine2 = truncate_line $combinedLine $freeSpace } else { $processedLine2 = "" } $finalLine = "$processedLine1$processedLine2" if ($stripansi) { $finalLine = $finalLine -replace $ansiRegex, "" } # Uncomment to test if all ANSI removed in stripansi mode # $finalLine = $finalLine -replace "`e", "\\e" Write-Output $finalLine } } # Reset the color on terminal if (-not $stripansi) { [console]::Write("`e[0m") } if (-not $stripansi) { Write-Output "$e[?25h" } else { Write-Output "" } } |