57dc91585d
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
122 lines
5.3 KiB
PowerShell
122 lines
5.3 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Generate assets/icon.ico from assets/icon.png — the Windows counterpart to
|
|
scripts/build-icons.sh (which produces icon.icns + the macOS tray PNGs).
|
|
|
|
.DESCRIPTION
|
|
Uses only the .NET Framework's System.Drawing (always present on Windows) —
|
|
no ImageMagick, no npm dependency. icon.png is the 1024x1024 raster already
|
|
rendered from assets/icon.svg by the macOS icon pipeline; this script
|
|
downscales it to the standard Windows icon sizes and packs them into a
|
|
classic, maximally-compatible BMP-based .ico (32bpp BGRA + AND mask). That
|
|
format is what electron-builder embeds in the .exe and what NSIS uses for
|
|
the installer icon, and it renders correctly on Windows 7 through 11.
|
|
|
|
Idempotent. Run from anywhere:
|
|
powershell -ExecutionPolicy Bypass -File desktop/scripts/build-win-icon.ps1
|
|
|
|
.NOTES
|
|
Author: Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
#>
|
|
$ErrorActionPreference = 'Stop'
|
|
Add-Type -AssemblyName System.Drawing
|
|
|
|
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$assets = Join-Path (Split-Path -Parent $here) 'assets'
|
|
$srcPng = Join-Path $assets 'icon.png'
|
|
$outIco = Join-Path $assets 'icon.ico'
|
|
|
|
if (-not (Test-Path $srcPng)) {
|
|
throw "icon.png not found at $srcPng. Generate it first (scripts/build-icons.sh renders it from icon.svg)."
|
|
}
|
|
|
|
# Standard Windows icon ladder. 256 is required by electron-builder; the small
|
|
# sizes keep the taskbar / Alt-Tab / tray crisp.
|
|
$sizes = 16, 24, 32, 48, 64, 128, 256
|
|
|
|
$src = [System.Drawing.Image]::FromFile($srcPng)
|
|
$entries = New-Object System.Collections.ArrayList
|
|
|
|
try {
|
|
foreach ($s in $sizes) {
|
|
$bmp = New-Object System.Drawing.Bitmap($s, $s, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
|
$g.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
|
|
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
|
|
$g.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality
|
|
$g.Clear([System.Drawing.Color]::Transparent)
|
|
$g.DrawImage($src, 0, 0, $s, $s)
|
|
$g.Dispose()
|
|
|
|
# Pull raw pixels: Format32bppArgb is stored little-endian as B,G,R,A —
|
|
# exactly the byte order a 32bpp DIB wants. Rows are top-down here.
|
|
$rect = New-Object System.Drawing.Rectangle(0, 0, $s, $s)
|
|
$data = $bmp.LockBits($rect, [System.Drawing.Imaging.ImageLockMode]::ReadOnly, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
$stride = $data.Stride
|
|
$buf = New-Object byte[] ($stride * $s)
|
|
[System.Runtime.InteropServices.Marshal]::Copy($data.Scan0, $buf, 0, $buf.Length)
|
|
$bmp.UnlockBits($data)
|
|
$bmp.Dispose()
|
|
|
|
# Build the DIB: BITMAPINFOHEADER(40) + XOR bitmap (bottom-up BGRA) +
|
|
# 1bpp AND mask (bottom-up, all zeros — alpha channel does the masking).
|
|
$ms = New-Object System.IO.MemoryStream
|
|
$bw = New-Object System.IO.BinaryWriter($ms)
|
|
$bw.Write([int]40) # biSize
|
|
$bw.Write([int]$s) # biWidth
|
|
$bw.Write([int]($s * 2)) # biHeight = XOR height + AND height
|
|
$bw.Write([int16]1) # biPlanes
|
|
$bw.Write([int16]32) # biBitCount
|
|
$bw.Write([int]0) # biCompression = BI_RGB
|
|
$bw.Write([int]0) # biSizeImage
|
|
$bw.Write([int]0) # biXPelsPerMeter
|
|
$bw.Write([int]0) # biYPelsPerMeter
|
|
$bw.Write([int]0) # biClrUsed
|
|
$bw.Write([int]0) # biClrImportant
|
|
|
|
# XOR pixels, bottom-up.
|
|
for ($y = $s - 1; $y -ge 0; $y--) {
|
|
$bw.Write($buf, $y * $stride, 4 * $s)
|
|
}
|
|
# AND mask: 1 bit/pixel, each row padded to a 4-byte boundary, all zero.
|
|
$maskRow = [int]([math]::Floor((($s + 31) / 32)) * 4)
|
|
$zeros = New-Object byte[] ($maskRow)
|
|
for ($y = 0; $y -lt $s; $y++) { $bw.Write($zeros, 0, $maskRow) }
|
|
|
|
$bw.Flush()
|
|
[void]$entries.Add([pscustomobject]@{ Size = $s; Data = $ms.ToArray() })
|
|
$bw.Dispose(); $ms.Dispose()
|
|
}
|
|
}
|
|
finally {
|
|
$src.Dispose()
|
|
}
|
|
|
|
# Assemble the .ico: ICONDIR header, then one ICONDIRENTRY per image, then data.
|
|
$out = New-Object System.IO.MemoryStream
|
|
$w = New-Object System.IO.BinaryWriter($out)
|
|
$w.Write([int16]0) # reserved
|
|
$w.Write([int16]1) # type = icon
|
|
$w.Write([int16]$entries.Count) # image count
|
|
|
|
$offset = 6 + 16 * $entries.Count
|
|
foreach ($e in $entries) {
|
|
$dim = if ($e.Size -ge 256) { 0 } else { $e.Size } # 0 means 256 in the dir
|
|
$w.Write([byte]$dim) # width
|
|
$w.Write([byte]$dim) # height
|
|
$w.Write([byte]0) # palette color count
|
|
$w.Write([byte]0) # reserved
|
|
$w.Write([int16]1) # color planes
|
|
$w.Write([int16]32) # bits per pixel
|
|
$w.Write([int]$e.Data.Length) # size of image data
|
|
$w.Write([int]$offset) # offset of image data
|
|
$offset += $e.Data.Length
|
|
}
|
|
foreach ($e in $entries) { $w.Write($e.Data, 0, $e.Data.Length) }
|
|
$w.Flush()
|
|
[System.IO.File]::WriteAllBytes($outIco, $out.ToArray())
|
|
$w.Dispose(); $out.Dispose()
|
|
|
|
Write-Output ("Wrote {0} ({1:N0} bytes, sizes: {2})" -f $outIco, (Get-Item $outIco).Length, ($sizes -join ', '))
|