feat(local-tool): macOS DMG 美化(create-dmg 背景圖 + Applications 捷徑)

需求:Mac 端 installer 體驗類比 Windows .exe — 進 DMG 就看到漂亮的視窗
背景 + 拖到 Applications 的視覺引導。

實作:
- installer/macos/ 新資料夾
  - make-dmg-background.py:動態生成 640×400 深色背景,配色對齊 Wails
    控制台 splash(#111827→#0B0F19 漸層 + #38BDF8 accent)
  - background.png + background@2x.png(1x + 2x Retina)
- Makefile dmg 拆三 target:
  - dmg:auto-detect,有 create-dmg 走 fancy,沒有 fallback plain(CI 無痛)
  - dmg-fancy:強制美化版(需 `brew install create-dmg`)
  - dmg-plain:原 hdiutil UDZO(保留為 fallback)
- Windows / Linux 流程零改動

驗證:
- `make dmg-fancy` 產出 157MB DMG,mount 後內容:app + Applications 捷徑
  + .background/background.png + .DS_Store(視窗樣式)
- `hdiutil verify` 通過

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-04-21 01:11:22 +08:00
parent d0b33f8c71
commit c1d2e2ddaa
5 changed files with 164 additions and 4 deletions

View File

@ -520,15 +520,48 @@ wails-linux: payload-linux ## ⚠️ 必須在 Linux runner 上執行wails bu
@du -sh visiona-local/build/bin/visiona-local @du -sh visiona-local/build/bin/visiona-local
# ── 安裝檔打包 ───────────────────────────────────────────────────── # ── 安裝檔打包 ─────────────────────────────────────────────────────
dmg: wails-macos ## hdiutil UDZO → dist/visiona-local.dmg dmg: wails-macos ## 美化 DMGcreate-dmg 有裝)或 plain DMGfallback→ dist/visiona-local.dmg
mkdir -p $(DIST) @mkdir -p $(DIST)
@rm -f $(DIST)/visiona-local.dmg
@if command -v create-dmg > /dev/null 2>&1; then \
$(MAKE) --no-print-directory dmg-fancy; \
else \
echo "⚠️ create-dmg 未安裝,使用 plain DMGhdiutil UDZO"; \
echo " 想要美化版本請執行brew install create-dmg"; \
$(MAKE) --no-print-directory dmg-plain; \
fi
@du -sh $(DIST)/visiona-local.dmg
@file $(DIST)/visiona-local.dmg
dmg-plain: ## hdiutil UDZO → dist/visiona-local.dmg無背景圖CI / fallback 用)
@mkdir -p $(DIST)
rm -f $(DIST)/visiona-local.dmg rm -f $(DIST)/visiona-local.dmg
hdiutil create -volname "visionA-local" \ hdiutil create -volname "visionA-local" \
-srcfolder visiona-local/build/bin/visiona-local.app \ -srcfolder visiona-local/build/bin/visiona-local.app \
-ov -format UDZO \ -ov -format UDZO \
$(DIST)/visiona-local.dmg $(DIST)/visiona-local.dmg
@du -sh $(DIST)/visiona-local.dmg
@file $(DIST)/visiona-local.dmg dmg-fancy: ## create-dmg 美化版 → dist/visiona-local.dmg需 brew install create-dmg
@if [ ! -d visiona-local/build/bin/visiona-local.app ]; then \
echo "❌ visiona-local/build/bin/visiona-local.app 不存在,請先跑 make wails-macos"; exit 1; \
fi
@if ! command -v create-dmg > /dev/null 2>&1; then \
echo "❌ create-dmg 未安裝請執行brew install create-dmg"; exit 1; \
fi
@mkdir -p $(DIST)
rm -f $(DIST)/visiona-local.dmg
create-dmg \
--volname "visionA-local" \
--background installer/macos/background.png \
--window-pos 200 120 \
--window-size 640 400 \
--icon-size 128 \
--icon "visiona-local.app" 180 200 \
--app-drop-link 460 200 \
--hide-extension "visiona-local.app" \
--no-internet-enable \
$(DIST)/visiona-local.dmg \
visiona-local/build/bin/visiona-local.app
exe-only: ## 只跑 iscc 打包 installer前置產物必須已存在不重 build wails/payload exe-only: ## 只跑 iscc 打包 installer前置產物必須已存在不重 build wails/payload
@if [ ! -f visiona-local/build/bin/visiona-local.exe ]; then \ @if [ ! -f visiona-local/build/bin/visiona-local.exe ]; then \

View File

@ -0,0 +1,42 @@
# macOS DMG 美化資源
## 檔案
| 檔案 | 用途 |
|------|------|
| `make-dmg-background.py` | 生成背景圖的 Python script需 Pillow |
| `background.png` | 640×400 DMG 背景圖1x |
| `background@2x.png` | 1280×800 DMG 背景圖Retinacreate-dmg 自動挑用) |
## 使用
```bash
# 一次性安裝
brew install create-dmg
# Build 美化 DMG
make dmg
```
`make dmg` 會自動偵測 `create-dmg`
- 有裝 → 產出美化版(深色背景 + 拖曳示意)
- 沒裝 → fallback 到 `make dmg-plain`(原本的 hdiutil UDZOCI 友善)
可直接指定 target
- `make dmg-fancy` — 強制美化版(`create-dmg` 未裝會報錯)
- `make dmg-plain` — 強制 plain 版
## 重新生成背景圖
改配色或文案時:
```bash
python3 installer/macos/make-dmg-background.py
```
會同時輸出 `background.png``background@2x.png`
## 設計對齊
背景圖配色對齊 Wails 控制台深色 splash`#111827``#0B0F19` 漸層 + `#38BDF8` accent
左側 app icon 位於 (180, 200),右側 Applications 捷徑位於 (460, 200),箭頭在中間。

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""
生成 DMG 美化背景圖640×400對齊 Wails 控制台深色 splash 風格
用法
python3 installer/macos/make-dmg-background.py
輸出
installer/macos/background.png (1x, 640×400)
installer/macos/background@2x.png (2x, 1280×800 Retina)
create-dmg 會自動挑 @2x 版本用於 Retina 螢幕
"""
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
OUT_DIR = Path(__file__).resolve().parent
W, H = 640, 400
BG_TOP = (17, 24, 39)
BG_BOTTOM = (11, 15, 25)
TEXT = (229, 231, 235)
MUTED = (148, 163, 184)
ACCENT = (56, 189, 248)
def make(scale: int, out_path: Path) -> None:
w, h = W * scale, H * scale
img = Image.new("RGB", (w, h), BG_TOP)
px = img.load()
for y in range(h):
t = y / (h - 1)
r = int(BG_TOP[0] + (BG_BOTTOM[0] - BG_TOP[0]) * t)
g = int(BG_TOP[1] + (BG_BOTTOM[1] - BG_TOP[1]) * t)
b = int(BG_TOP[2] + (BG_BOTTOM[2] - BG_TOP[2]) * t)
for x in range(w):
px[x, y] = (r, g, b)
draw = ImageDraw.Draw(img)
try:
font_title = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 22 * scale)
font_hint = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 14 * scale)
except OSError:
font_title = ImageFont.load_default()
font_hint = ImageFont.load_default()
title = "Drag visionA-local to Applications"
tw = draw.textlength(title, font=font_title)
draw.text(((w - tw) / 2, 28 * scale), title, fill=TEXT, font=font_title)
hint = "拖曳圖示到右邊的 Applications 即可安裝"
hw = draw.textlength(hint, font=font_hint)
draw.text(((w - hw) / 2, 60 * scale), hint, fill=MUTED, font=font_hint)
# 箭頭位置:左右兩個 icon 約在 y=230中心。icon 本身 128×128由 create-dmg 擺。
# create-dmg 預設 app icon x=180, Applications x=460y=200。箭頭畫在中間 240-400 區段。
arrow_y = 200 * scale
arrow_x1 = 260 * scale
arrow_x2 = 380 * scale
line_w = 3 * scale
draw.line([(arrow_x1, arrow_y), (arrow_x2, arrow_y)], fill=ACCENT, width=line_w)
# 箭頭頭
head = 12 * scale
draw.polygon(
[
(arrow_x2, arrow_y),
(arrow_x2 - head, arrow_y - head // 2 - 2 * scale),
(arrow_x2 - head, arrow_y + head // 2 + 2 * scale),
],
fill=ACCENT,
)
img.save(out_path, "PNG", optimize=True)
print(f"wrote {out_path} ({out_path.stat().st_size // 1024} KB)")
def main() -> None:
make(1, OUT_DIR / "background.png")
make(2, OUT_DIR / "background@2x.png")
if __name__ == "__main__":
main()