From c1d2e2ddaaf76d1ede60b2b7ad2604a66a25027f Mon Sep 17 00:00:00 2001 From: jim800121chen Date: Tue, 21 Apr 2026 01:11:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(local-tool):=20macOS=20DMG=20=E7=BE=8E?= =?UTF-8?q?=E5=8C=96=EF=BC=88create-dmg=20=E8=83=8C=E6=99=AF=E5=9C=96=20+?= =?UTF-8?q?=20Applications=20=E6=8D=B7=E5=BE=91=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 需求: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) --- local-tool/Makefile | 41 ++++++++- local-tool/installer/macos/README.md | 42 +++++++++ local-tool/installer/macos/background.png | Bin 0 -> 6996 bytes local-tool/installer/macos/background@2x.png | Bin 0 -> 16296 bytes .../installer/macos/make-dmg-background.py | 85 ++++++++++++++++++ 5 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 local-tool/installer/macos/README.md create mode 100644 local-tool/installer/macos/background.png create mode 100644 local-tool/installer/macos/background@2x.png create mode 100644 local-tool/installer/macos/make-dmg-background.py diff --git a/local-tool/Makefile b/local-tool/Makefile index 06ca92a..87f2ac2 100644 --- a/local-tool/Makefile +++ b/local-tool/Makefile @@ -520,15 +520,48 @@ wails-linux: payload-linux ## ⚠️ 必須在 Linux runner 上執行:wails bu @du -sh visiona-local/build/bin/visiona-local # ── 安裝檔打包 ───────────────────────────────────────────────────── -dmg: wails-macos ## hdiutil UDZO → dist/visiona-local.dmg - mkdir -p $(DIST) +dmg: wails-macos ## 美化 DMG(create-dmg 有裝)或 plain DMG(fallback)→ dist/visiona-local.dmg + @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 DMG(hdiutil 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 hdiutil create -volname "visionA-local" \ -srcfolder visiona-local/build/bin/visiona-local.app \ -ov -format UDZO \ $(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 @if [ ! -f visiona-local/build/bin/visiona-local.exe ]; then \ diff --git a/local-tool/installer/macos/README.md b/local-tool/installer/macos/README.md new file mode 100644 index 0000000..95f53b8 --- /dev/null +++ b/local-tool/installer/macos/README.md @@ -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 背景圖(Retina,create-dmg 自動挑用) | + +## 使用 + +```bash +# 一次性安裝 +brew install create-dmg + +# Build 美化 DMG +make dmg +``` + +`make dmg` 會自動偵測 `create-dmg`: +- 有裝 → 產出美化版(深色背景 + 拖曳示意) +- 沒裝 → fallback 到 `make dmg-plain`(原本的 hdiutil UDZO,CI 友善) + +可直接指定 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),箭頭在中間。 diff --git a/local-tool/installer/macos/background.png b/local-tool/installer/macos/background.png new file mode 100644 index 0000000000000000000000000000000000000000..f9b37113d9c660d1a22fa251d5b1f9fb3eb0384b GIT binary patch literal 6996 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 PEdv?ondp{iJ3Rg`5a$Jw literal 0 HcmV?d00001 diff --git a/local-tool/installer/macos/background@2x.png b/local-tool/installer/macos/background@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..46c3b45f820c16899a1cffe59a08da2af63c5a27 GIT binary patch literal 16296 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 H51;=pYaxn? literal 0 HcmV?d00001 diff --git a/local-tool/installer/macos/make-dmg-background.py b/local-tool/installer/macos/make-dmg-background.py new file mode 100644 index 0000000..37f0026 --- /dev/null +++ b/local-tool/installer/macos/make-dmg-background.py @@ -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=460(y=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()