- 删除通用 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 规范文档
82 lines
3.8 KiB
Markdown
82 lines
3.8 KiB
Markdown
## 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 分支实现。macOS(osascript)和 Linux(zenity)不变。
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- **[syscall 跨架构]** `MessageBoxW` 的 `syscall.NewLazyDLL` 仅在 Windows 上有效 → 使用 `runtime.GOOS` 守卫,非 Windows 不会执行该路径,编译时通过 build 文件或运行时判断确保不触发
|
||
- **[ICO 嵌入体积]** `icon.ico` 270KB,已在 `embedfs` 中,不增加新体积 → 无风险
|
||
- **[Makefile 兼容性]** 删除 `desktop` target 后,CI/本地脚本如果引用它需更新 → 需检查是否有外部引用
|