fix: 修复 dsh.ico 帧偏移 bug + 托盘图标加固 + BOM 经验
- tools/repair-ico.ps1: 检测并修复 ICO 帧偏移量未计目录长度的损坏模式 - assets/dsh.ico: 已修复(原文件偏移全错,.NET Icon 无法加载) - tools/dsh-app.ps1: 归档加固版托盘脚本(图标加载兜底) - tools/dsh-app.ps1.README.md: 修复记录 + UTF-8 BOM 踩坑
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,117 @@
|
||||
# DeepSeek Harness - 托盘应用
|
||||
# 双击桌面快捷方式启动:拉起 dsh web 服务(若未运行),右下角托盘常驻,
|
||||
# 右键菜单可"打开主界面"(Edge 独立应用窗口)或"退出"(停止服务并退出)。
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$DshBin = 'C:\Users\a1703\AppData\Local\npm-cache\_npx\1e7f6d9597241db0\node_modules\@deepseek-ai\dsh\lib\bin.js'
|
||||
$IconFile = 'C:\Users\a1703\.dsh\assets\dsh.ico'
|
||||
$LogDir = 'C:\Users\a1703\.dsh\logs'
|
||||
$LogFile = Join-Path $LogDir 'dsh-app.log'
|
||||
$Url = 'http://127.0.0.1:3080'
|
||||
$Port = 3080
|
||||
$WorkDir = 'C:\Users\a1703'
|
||||
$Edge = @('C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe', 'C:\Program Files\Microsoft\Edge\Application\msedge.exe') | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
|
||||
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
|
||||
|
||||
function Log([string]$msg) {
|
||||
try { Add-Content -Path $LogFile -Value ("{0} {1}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg) -Encoding UTF8 } catch {}
|
||||
}
|
||||
|
||||
function Test-Port([int]$port) {
|
||||
try {
|
||||
$c = New-Object System.Net.Sockets.TcpClient
|
||||
$ar = $c.BeginConnect('127.0.0.1', $port, $null, $null)
|
||||
if (-not $ar.AsyncWaitHandle.WaitOne(500)) { $c.Close(); return $false }
|
||||
$c.EndConnect($ar); $c.Close(); return $true
|
||||
} catch { return $false }
|
||||
}
|
||||
|
||||
function Stop-Dsh {
|
||||
Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.CommandLine -like '*@deepseek-ai\dsh*lib*bin.js*' } |
|
||||
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
function Stop-EdgeApp {
|
||||
Get-CimInstance Win32_Process -Filter "Name='msedge.exe'" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.CommandLine -like '*--app=http://127.0.0.1:3080*' } |
|
||||
ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
function Open-Main {
|
||||
try {
|
||||
if ($Edge) { Start-Process $Edge -ArgumentList "--app=$Url" }
|
||||
else { Start-Process $Url }
|
||||
Log 'opened main window'
|
||||
} catch { Log "open main failed: $_" }
|
||||
}
|
||||
|
||||
# 单实例保护:已有托盘在运行则直接弹出主界面并退出本进程
|
||||
$mutex = New-Object System.Threading.Mutex($true, 'DSHWebTrayApp')
|
||||
if (-not $mutex.WaitOne(0)) {
|
||||
Log 'another instance is running; opening main window only'
|
||||
Open-Main
|
||||
exit
|
||||
}
|
||||
|
||||
# 确保服务在跑
|
||||
if (-not (Test-Port $Port)) {
|
||||
Log 'dsh not listening; starting it'
|
||||
try {
|
||||
Start-Process -FilePath 'node' -ArgumentList @($DshBin, 'web') -WorkingDirectory $WorkDir -WindowStyle Hidden `
|
||||
-RedirectStandardOutput (Join-Path $LogDir 'dsh-web.out.log') -RedirectStandardError (Join-Path $LogDir 'dsh-web.err.log')
|
||||
} catch { Log "start dsh failed: $_" }
|
||||
$ready = $false
|
||||
for ($i = 0; $i -lt 120; $i++) {
|
||||
Start-Sleep -Milliseconds 500
|
||||
if (Test-Port $Port) { $ready = $true; break }
|
||||
}
|
||||
if ($ready) { Log 'dsh is ready' } else { Log 'dsh did not become ready within 60s' }
|
||||
} else {
|
||||
Log 'dsh already listening'
|
||||
}
|
||||
|
||||
# 托盘图标与菜单
|
||||
$notify = New-Object System.Windows.Forms.NotifyIcon
|
||||
try {
|
||||
$notify.Icon = New-Object System.Drawing.Icon($IconFile)
|
||||
} catch {
|
||||
Log "icon load failed: $_; falling back"
|
||||
try {
|
||||
# 兜底1:同目录下尝试修复版/其他 .ico
|
||||
$alt = Get-ChildItem (Split-Path $IconFile) -Filter '*.ico' -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($alt) { $notify.Icon = New-Object System.Drawing.Icon($alt.FullName) }
|
||||
} catch {}
|
||||
if (-not $notify.Icon) {
|
||||
try { $notify.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon((Get-Process -Id $PID).Path) } catch {}
|
||||
}
|
||||
}
|
||||
$notify.Text = 'DeepSeek Harness'
|
||||
$notify.Visible = $true
|
||||
|
||||
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
||||
$itemOpen = New-Object System.Windows.Forms.ToolStripMenuItem('打开主界面')
|
||||
$itemOpen.Add_Click({ Open-Main })
|
||||
$itemExit = New-Object System.Windows.Forms.ToolStripMenuItem('退出')
|
||||
$itemExit.Add_Click({
|
||||
Log 'exit requested'
|
||||
try { $notify.Visible = $false } catch {}
|
||||
Stop-EdgeApp
|
||||
Stop-Dsh
|
||||
try { $ctx.ExitThread() } catch {}
|
||||
})
|
||||
$menu.Items.Add($itemOpen) | Out-Null
|
||||
$menu.Items.Add($itemExit) | Out-Null
|
||||
$notify.ContextMenuStrip = $menu
|
||||
$notify.Add_MouseDoubleClick({
|
||||
if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) { Open-Main }
|
||||
})
|
||||
|
||||
$ctx = New-Object System.Windows.Forms.ApplicationContext
|
||||
Log 'tray app running'
|
||||
Open-Main
|
||||
[System.Windows.Forms.Application]::Run($ctx)
|
||||
@@ -0,0 +1,28 @@
|
||||
# dsh-app.ps1 — DeepSeek Harness 托盘应用壳(归档参考版)
|
||||
|
||||
> 实际部署位置:`C:\Users\a1703\.dsh\bin\dsh-app.ps1`(桌面快捷方式 `DeepSeek Harness.lnk` 指向它)。
|
||||
> 本文件为当前版本的归档副本。**必须保留 UTF-8 BOM**(见下方"踩坑"),
|
||||
> 机器相关路径($DshBin 的 npx 缓存位置)在新机器需修改。
|
||||
|
||||
## 功能
|
||||
|
||||
- 双击快捷方式:检测 3080 端口 → 未运行则拉起 `node <dsh bin.js> web` → 托盘常驻
|
||||
- 托盘右键:打开主界面(Edge `--app` 独立窗口)/ 退出(停止服务并退出)
|
||||
- 单实例保护(Mutex)
|
||||
|
||||
## 2026-08-17 修复记录
|
||||
|
||||
1. **托盘图标报错 `操作成功完成`**:根因是 `dsh.ico` 帧偏移量少算了目录长度(6+count*16),
|
||||
.NET `System.Drawing.Icon` 无法解析。修复:`tools/repair-ico.ps1`(检测首帧偏移为 0 的
|
||||
损坏模式 → 所有帧偏移 + 目录长度 → 验证)。本机 `dsh.ico` 与 web 前端 `favicon.ico` 均已修复。
|
||||
2. **图标加载加固**:`$notify.Icon` 加载失败时回退(同目录其他 .ico → 系统图标)并写日志。
|
||||
|
||||
## 踩坑:UTF-8 BOM 必须保留
|
||||
|
||||
- Windows PowerShell 5.1 读取**无 BOM** 的 .ps1 按 ANSI(GBK) 解析 → 中文变乱码、
|
||||
引号被吞 → 整个脚本解析失败(表现为托盘静默退出)。
|
||||
- 用 `edit`/`write` 工具改写本文件后必须补 BOM:
|
||||
```powershell
|
||||
$c = [System.IO.File]::ReadAllText($f, [System.Text.Encoding]::UTF8)
|
||||
[System.IO.File]::WriteAllText($f, $c, (New-Object System.Text.UTF8Encoding($true)))
|
||||
```
|
||||
@@ -0,0 +1,47 @@
|
||||
# repair-ico.ps1 - Fix broken ICO files whose frame offsets omit the directory size.
|
||||
#
|
||||
# Background: some generators write ICO frame offsets starting at 0 (not accounting
|
||||
# for the 6 + count*16 directory), which makes .NET Framework System.Drawing.Icon
|
||||
# throw "The operation completed successfully". This script detects that pattern
|
||||
# and adds the directory length to every frame offset.
|
||||
#
|
||||
# Usage: powershell -NoProfile -ExecutionPolicy Bypass -File repair-ico.ps1 <file.ico> [more.ico ...]
|
||||
# Each file is backed up as <file>.bak-<timestamp> before being rewritten.
|
||||
|
||||
param([Parameter(Mandatory=$true, Position=0)][string]$IcoPath)
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
foreach ($path in @($IcoPath)) {
|
||||
$full = (Resolve-Path $path -ErrorAction SilentlyContinue).Path
|
||||
if (-not $full -or -not (Test-Path $full)) { Write-Host "SKIP (not found): $path"; continue }
|
||||
$bytes = [System.IO.File]::ReadAllBytes($full)
|
||||
if ($bytes.Length -lt 22) { Write-Host "SKIP (too small): $path"; continue }
|
||||
|
||||
$count = [BitConverter]::ToUInt16($bytes, 4)
|
||||
if ($count -lt 1 -or $count -gt 64) { Write-Host "SKIP (odd frame count $count): $path"; continue }
|
||||
$dirLen = 6 + $count * 16
|
||||
|
||||
# Detect the broken pattern: first frame offset is 0 (points at the directory itself)
|
||||
$off0 = [BitConverter]::ToUInt32($bytes, 6 + 12)
|
||||
if ($off0 -ne 0) { Write-Host "OK (first offset=$off0, no fix needed): $path"; continue }
|
||||
|
||||
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||
$bak = "$full.bak-$stamp"
|
||||
Copy-Item $full $bak -Force
|
||||
|
||||
$b = [byte[]]$bytes.Clone()
|
||||
for ($i = 0; $i -lt $count; $i++) {
|
||||
$o = 6 + $i * 16 + 12
|
||||
$v = [BitConverter]::ToUInt32($b, $o) + $dirLen
|
||||
[BitConverter]::GetBytes($v).CopyTo($b, $o)
|
||||
}
|
||||
[System.IO.File]::WriteAllBytes($full, $b)
|
||||
|
||||
try {
|
||||
$ic = [System.Drawing.Icon]::new($full)
|
||||
$ok = "OK $($ic.Width)x$($ic.Height)"
|
||||
$ic.Dispose()
|
||||
} catch { $ok = "STILL FAILS: $($_.Exception.InnerException.Message)" }
|
||||
Write-Host "FIXED: $full (backup $bak) verify: $ok"
|
||||
}
|
||||
Reference in New Issue
Block a user