1
0
Files
nex/openspec/changes/fix-windows-desktop-packaging/design.md
lanyuanxiaoyao 64dc66afa6 fix: Windows 桌面应用打包问题修复
- 删除通用 desktop target,重命名 platform targets 为简短形式 (desktop-mac/win/linux)
- 构建产物文件名统一为 nex-{os}-{arch}[.exe] 格式
- Windows 托盘图标使用 .ico 格式(运行时按平台选择)
- Windows 原生对话框使用 user32.MessageBoxW 替代 msg * 命令
- 更新 README.md 和 package-macos.sh 中的引用
- 添加单元测试覆盖 MessageBoxW 封装和图标选择逻辑
- 同步更新 desktop-app spec 规范文档
2026-04-22 23:20:39 +08:00

82 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
Nex 桌面应用是一个将后端服务Go/Gin与前端静态资源embed.FS打包为单一可执行文件的跨平台应用。当前 Windows 构建存在三类问题:
1. **通用 `desktop` target 无平台感知**:输出 `build/nex``.exe` 后缀,且缺少 `-H=windowsgui` linker flag 导致控制台窗口闪现
2. **系统托盘图标加载失败**`getlantern/systray` 在 Windows 上期望 ICO 格式,代码传入了 64x64 的 PNG
3. **`showError`/`showAbout` 使用 `msg *`**Windows Home 版本可能不可用,配合 `-H=windowsgui` 后行为不可预测,且不支持自定义标题栏
项目已有 `assets/icon.ico`256x256但代码未使用。
## Goals / Non-Goals
**Goals:**
- Windows 构建产物可直接双击运行(.exe 后缀、无控制台窗口)
- 系统托盘图标在所有平台上正确加载
- Windows 上使用原生 `MessageBoxW` 对话框替代 `msg *`
- Makefile target 命名简洁统一
**Non-Goals:**
- 不引入新的第三方依赖
- 不改变 macOS/Linux 上的现有行为
- 不涉及应用签名或代码公证(属于发布流程)
- 不重构整体打包架构
## Decisions
### 1. 删除通用 `desktop` target重命名平台 target
**决策**:删除 `desktop` target`desktop-darwin`/`desktop-windows`/`desktop-linux` 重命名为 `desktop-mac`/`desktop-win`/`desktop-linux`
**理由**:通用 target 在跨平台构建时必然需要条件判断,增加复杂度。按平台分离更明确,且项目已有先例。短命名 `win`/`mac`/`linux` 更简洁。
**产物命名统一**`nex-{os}-{arch}[.exe]`
- `nex-mac-arm64``nex-mac-amd64`
- `nex-win-amd64.exe`
- `nex-linux-amd64`
### 2. 托盘图标运行时按平台选择格式
**决策**:在 `setupSystray` 中根据 `runtime.GOOS` 选择图标文件:
- Windows加载 `assets/icon.ico`256x256 ICO
- 其他:加载 `assets/icon.png`PNG
**备选方案**
- ~~Build tags + 编译时选择~~:增加文件数,维护成本高
- ~~所有平台统一用 ICO~~Linux/macOS 的 systray 实现对 ICO 支持不一致
**理由**:运行时判断最简单,两个文件都已通过 `embedfs.Assets``assets/*`)嵌入,零额外成本。
### 3. Windows 原生对话框使用 `user32.MessageBoxW`
**决策**:通过 `syscall` 调用 `user32.dll``MessageBoxW`,替换 `msg *`
**实现方式**
```go
var (
user32 = syscall.NewLazyDLL("user32.dll")
procMessageBoxW = user32.NewProc("MessageBoxW")
)
func messageBox(title, message string) {
procMessageBoxW.Call(0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(message))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), 0x10)
}
```
**备选方案**
- ~~继续用 `msg *`~~:不解决 Home 版不可用、标题栏不支持的问题
- ~~`rundll32` 调用~~:同样不可靠
- ~~引入 `lxn/walk` 等 GUI 库~~:引入重依赖,过度
**理由**`MessageBoxW` 是 Windows 原生 API所有版本都有`-H=windowsgui` 完美兼容,支持标题栏和图标类型,零依赖。使用 `syscall`(非 `unsafe` 外部依赖)即可。
### 4. `showError`/`showAbout` 统一用平台 switch
**决策**:保持现有的 `switch runtime.GOOS` 结构,仅替换 Windows 分支实现。macOSosascript和 Linuxzenity不变。
## Risks / Trade-offs
- **[syscall 跨架构]** `MessageBoxW``syscall.NewLazyDLL` 仅在 Windows 上有效 → 使用 `runtime.GOOS` 守卫,非 Windows 不会执行该路径,编译时通过 build 文件或运行时判断确保不触发
- **[ICO 嵌入体积]** `icon.ico` 270KB已在 `embedfs` 中,不增加新体积 → 无风险
- **[Makefile 兼容性]** 删除 `desktop` target 后CI/本地脚本如果引用它需更新 → 需检查是否有外部引用