diff --git a/cmd/arcrun-tray/assets/store/README.md b/cmd/arcrun-tray/assets/store/README.md new file mode 100644 index 0000000..1a34155 --- /dev/null +++ b/cmd/arcrun-tray/assets/store/README.md @@ -0,0 +1,38 @@ +# Microsoft Store 圖示素材(msix) + +這裡的 PNG 是 **產物**,但**隨 repo 版控**——因為 `build-msix.sh` 打包時只做 `cp`, +不在打包當下重算(打包機不一定拿得到 CIS 素材)。 + +## 真身在哪 + +品牌素材真身=`InkStoneCo/arcrun-cis/`(leo 的 CIS,2026-07-31)。 +**不要在這裡改圖**;要改品牌就改 CIS,然後重跑產生器。 + +## 重新產生 + +```bash +python3 gen-store-assets.py # 需要 Pillow +# CIS 不在預設位置時: +ARCRUN_CIS=/path/to/arcrun-cis python3 gen-store-assets.py +``` + +## 每個檔案是什麼 + +| 檔案 | 尺寸 | 用途 | 取材 | +|---|---|---|---| +| `Square44x44Logo.png` | 44×44 | 應用程式清單、工作列 | 方形 icon | +| `StoreLogo.png` | 50×50 | Store 頁面小圖 | 方形 icon | +| `Square71x71Logo.png` | 71×71 | 小磚 | 方形 icon | +| `Square150x150Logo.png` | 150×150 | 中磚(預設磚) | 方形 icon | +| `Square310x310Logo.png` | 310×310 | 大磚 | 方形 icon | +| `Wide310x150Logo.png` | 310×150 | 寬磚 | **橫式 lockup** | +| `Square44x44Logo.targetsize-{16,24}.png` | 16/24 | 工作列小圖 | **雙 chevron** | +| `Square44x44Logo.targetsize-{32,48,256}.png` | 32/48/256 | 檔案總管各檢視 | 方形 icon | + +## 兩條「為什麼不是直接縮」的理由(別再改回去) + +1. **寬磚不能用方形 icon 硬拉**。舊版寫 `sips -z 150 310 icon.png`, + `sips -z` 會**照給定的長寬直接拉扁**——實測產出的字符明顯變形, + 違反 CIS 的 Don't「never skew or stretch」。寬磚要用橫式 lockup。 +2. **16/24px 不能用完整方形 icon**。CIS 明寫「26px 以下降級成 chevron」: + 方形 icon 裡的字符只佔畫面高度約 28%,16px 下只剩約 4px 高,糊成一團。 diff --git a/cmd/arcrun-tray/assets/store/Square150x150Logo.png b/cmd/arcrun-tray/assets/store/Square150x150Logo.png new file mode 100644 index 0000000..56fce14 Binary files /dev/null and b/cmd/arcrun-tray/assets/store/Square150x150Logo.png differ diff --git a/cmd/arcrun-tray/assets/store/Square310x310Logo.png b/cmd/arcrun-tray/assets/store/Square310x310Logo.png new file mode 100644 index 0000000..ee5deb8 Binary files /dev/null and b/cmd/arcrun-tray/assets/store/Square310x310Logo.png differ diff --git a/cmd/arcrun-tray/assets/store/Square44x44Logo.png b/cmd/arcrun-tray/assets/store/Square44x44Logo.png new file mode 100644 index 0000000..0bcb8b3 Binary files /dev/null and b/cmd/arcrun-tray/assets/store/Square44x44Logo.png differ diff --git a/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-16.png b/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-16.png new file mode 100644 index 0000000..55f4e82 Binary files /dev/null and b/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-16.png differ diff --git a/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-24.png b/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-24.png new file mode 100644 index 0000000..4b59996 Binary files /dev/null and b/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-24.png differ diff --git a/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-256.png b/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-256.png new file mode 100644 index 0000000..3b91792 Binary files /dev/null and b/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-256.png differ diff --git a/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-32.png b/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-32.png new file mode 100644 index 0000000..be02120 Binary files /dev/null and b/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-32.png differ diff --git a/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-48.png b/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-48.png new file mode 100644 index 0000000..33c3528 Binary files /dev/null and b/cmd/arcrun-tray/assets/store/Square44x44Logo.targetsize-48.png differ diff --git a/cmd/arcrun-tray/assets/store/Square71x71Logo.png b/cmd/arcrun-tray/assets/store/Square71x71Logo.png new file mode 100644 index 0000000..62821ba Binary files /dev/null and b/cmd/arcrun-tray/assets/store/Square71x71Logo.png differ diff --git a/cmd/arcrun-tray/assets/store/StoreLogo.png b/cmd/arcrun-tray/assets/store/StoreLogo.png new file mode 100644 index 0000000..bbf8626 Binary files /dev/null and b/cmd/arcrun-tray/assets/store/StoreLogo.png differ diff --git a/cmd/arcrun-tray/assets/store/Wide310x150Logo.png b/cmd/arcrun-tray/assets/store/Wide310x150Logo.png new file mode 100644 index 0000000..20fea93 Binary files /dev/null and b/cmd/arcrun-tray/assets/store/Wide310x150Logo.png differ diff --git a/cmd/arcrun-tray/assets/store/gen-store-assets.py b/cmd/arcrun-tray/assets/store/gen-store-assets.py new file mode 100644 index 0000000..096df63 --- /dev/null +++ b/cmd/arcrun-tray/assets/store/gen-store-assets.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""重新產生 Microsoft Store(msix)的圖示素材。 + +素材真身=leo 的 CIS(`InkStoneCo/arcrun-cis/`),本腳本只做「等比縮 + 置中合成」。 + +守 CIS 的 Don't:**不拉扁、不歪斜、不加第二個顏色**。 +- 方形磚 → 用方形 icon(`arcrun-icon-ink-1024.png`)等比縮。 +- 寬磚 310×150 → 用**橫式 lockup**(`arcrun-lockup-h-paper-on-ink.png`)置中於墨底。 + 不能拿方形 icon 硬拉成 310×150——舊版 `sips -z 150 310` 就是這樣把方形字符拉扁的。 +- targetsize-16/24(工作列小圖)→ CIS 規定 26px 以下降級成 chevron, + 完整字符在這尺寸只剩約 4px 高,會糊掉。 + +用法(需要 Pillow): + python3 gen-store-assets.py +產物寫回本目錄、隨 repo 版控;`build-msix.sh` 只負責 cp 進包裡。 +""" +import os +from pathlib import Path + +from PIL import Image, ImageDraw + +HERE = Path(__file__).resolve().parent +# CIS 住在 InkStoneCo 頂層。預設往上找,找不到時用 ARCRUN_CIS 指過去。 +CIS = Path(os.environ["ARCRUN_CIS"]) if os.environ.get("ARCRUN_CIS") else None +if CIS is None: + for parent in HERE.parents: + cand = parent / "arcrun-cis" + if cand.is_dir(): + CIS = cand + break + +INK = (23, 24, 26, 255) +PAPER = (253, 252, 251, 255) + + +def load(name: str) -> Image.Image: + if CIS is None or not (CIS / name).exists(): + raise SystemExit( + f"❌ 找不到 CIS 素材 {name}(找過 {CIS})\n" + f" 指過去:ARCRUN_CIS=/path/to/arcrun-cis python3 {Path(__file__).name}") + return Image.open(CIS / name).convert("RGBA") + + +def chevron_on_ink(size: int, inset: float = 0.20) -> Image.Image: + """雙 chevron(paper)置中於墨底——給 26px 以下的小尺寸用。 + + 幾何與 CIS `chevron-double.svg` 同一組座標(24-grid、stroke 2.8)。 + 先超取樣畫再縮,邊緣才不會鋸齒。 + """ + ss = size * 16 + unit = ss / 24.0 + layer = Image.new("RGBA", (ss, ss), (0, 0, 0, 0)) + d = ImageDraw.Draw(layer) + w = max(1, round(2.8 * unit)) + for dx in (0, 7): + d.line([((5 + dx) * unit, 5 * unit), + ((12 + dx) * unit, 12 * unit), + ((5 + dx) * unit, 19 * unit)], + fill=PAPER, width=w, joint="curve") + + art = layer.crop(layer.split()[3].getbbox()) + target = ss * (1 - 2 * inset) + scale = target / max(art.size) + nw, nh = max(1, round(art.size[0] * scale)), max(1, round(art.size[1] * scale)) + art = art.resize((nw, nh), Image.LANCZOS) + + out = Image.new("RGBA", (ss, ss), INK) + out.paste(art, ((ss - nw) // 2, (ss - nh) // 2), art) + return out.resize((size, size), Image.LANCZOS) + + +def wide_tile(w: int, h: int, pad_ratio: float = 0.14) -> Image.Image: + """寬磚:橫式 lockup 等比 fit 進墨底,四周留白(CIS clear space)。""" + lock = load("arcrun-lockup-h-paper-on-ink.png") + canvas = Image.new("RGBA", (w, h), INK) + lw, lh = lock.size + scale = min(w * (1 - 2 * pad_ratio) / lw, h * (1 - 2 * pad_ratio) / lh) + nw, nh = max(1, round(lw * scale)), max(1, round(lh * scale)) + art = lock.resize((nw, nh), Image.LANCZOS) + canvas.paste(art, ((w - nw) // 2, (h - nh) // 2), art) + return canvas + + +def main() -> None: + square = load("arcrun-icon-ink-1024.png") + made = [] + + for name, s in [("Square44x44Logo", 44), ("StoreLogo", 50), + ("Square71x71Logo", 71), ("Square150x150Logo", 150), + ("Square310x310Logo", 310)]: + square.resize((s, s), Image.LANCZOS).save(HERE / f"{name}.png") + made.append((f"{name}.png", s, s)) + + # 工作列 / 開始功能表多尺寸:26px 以下用 chevron(CIS 規定),其餘用方形 icon + for s in (16, 24, 32, 48, 256): + img = chevron_on_ink(s) if s <= 26 else square.resize((s, s), Image.LANCZOS) + img.save(HERE / f"Square44x44Logo.targetsize-{s}.png") + made.append((f"Square44x44Logo.targetsize-{s}.png", s, s)) + + wide_tile(310, 150).save(HERE / "Wide310x150Logo.png") + made.append(("Wide310x150Logo.png", 310, 150)) + + for n, w, h in made: + print(f"✅ {n}: {w}x{h}") + + +if __name__ == "__main__": + main() diff --git a/cmd/arcrun-tray/assets/tray-template.png b/cmd/arcrun-tray/assets/tray-template.png new file mode 100644 index 0000000..a25660e Binary files /dev/null and b/cmd/arcrun-tray/assets/tray-template.png differ diff --git a/cmd/arcrun-tray/assets/tray-windows.png b/cmd/arcrun-tray/assets/tray-windows.png new file mode 100644 index 0000000..555f35f Binary files /dev/null and b/cmd/arcrun-tray/assets/tray-windows.png differ diff --git a/cmd/arcrun-tray/build-msix.sh b/cmd/arcrun-tray/build-msix.sh new file mode 100644 index 0000000..b093886 --- /dev/null +++ b/cmd/arcrun-tray/build-msix.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# build-msix.sh — 把 Windows 托盤打包成 Microsoft Store 用的 .msix(rag-beta 關 8)。 +# +# 🎯 重點:**在 Mac 上就打得出 msix**,不需要 Windows 機器、不需要 MakeAppx。 +# 靠的是微軟自家的開源跨平台工具 msix-packaging 的 `makemsix`。 +# (2026-07-31 本機實測通過:pack 出 17 MB msix,unpack 回讀 SHA 與原 exe 一致。) +# +# ⚠️ **不必自購簽章憑證**:送 Store 的 msix 不用簽——微軟過審後會用自己的憑證重簽。 +# (官方原文見 docs/store-submission.md §2)。但**側載**(不走 Store)就必須自己簽。 +# +# 前置(一次就好): +# brew install mingw-w64 cmake icu4c # 交叉編譯器 + 建置工具 + Xerces 需要的 ICU +# bash build-msix.sh --setup # 建 makemsix(約 5-10 分鐘,只需跑一次) +# +# 用法: +# bash build-msix.sh # 產出 dist-msix/ArcrunRAG.msix +# 產物:dist-msix/ArcrunRAG.msix(**未簽章,這是對的**——Store 會重簽) +set -euo pipefail +cd "$(dirname "$0")" + +MSIX_SDK_DIR="${MSIX_SDK_DIR:-$HOME/.cache/msix-packaging}" +MAKEMSIX="$MSIX_SDK_DIR/.vs/bin/makemsix" + +# ── --setup:建 makemsix(一次性)──────────────────────────────────────── +if [[ "${1:-}" == "--setup" ]]; then + echo "① clone msix-packaging(微軟開源,匿名讀取,不碰 GitHub 寫入)" + [[ -d "$MSIX_SDK_DIR" ]] || git clone --depth 1 https://github.com/microsoft/msix-packaging.git "$MSIX_SDK_DIR" + + echo "② 修 C++ 標準:14 → 17" + # 為什麼要修:這套 SDK 停在 2022 年、寫死 C++14,但 Homebrew 現在的 ICU 標頭 + # 用了 std::is_same_v 等 C++17 語法 ⇒ 不修必炸 'no template named is_same_v'。 + # **兩個檔都要改**——頂層改了還會被 xerces 子專案的設定蓋回去(實際踩過的坑)。 + for f in "$MSIX_SDK_DIR/CMakeLists.txt" "$MSIX_SDK_DIR/lib/xerces/CMakeLists.txt"; do + sed -i.bak 's/set(CMAKE_CXX_STANDARD 14)/set(CMAKE_CXX_STANDARD 17)/' "$f" + done + + ICU_PREFIX="$(brew --prefix icu4c@77 2>/dev/null || brew --prefix icu4c)" + [[ -f "$ICU_PREFIX/include/unicode/uset.h" ]] || { echo "❌ 找不到 ICU 標頭。先跑:brew install icu4c" >&2; exit 1; } + + echo "③ cmake 配置(--pack 才會有打包功能,預設是關的)" + mkdir -p "$MSIX_SDK_DIR/.vs" && cd "$MSIX_SDK_DIR/.vs" + cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DXML_PARSER=xerces -DSKIP_BUNDLES=off \ + -DUSE_VALIDATION_PARSER=on -DMSIX_PACK=on -DMSIX_SAMPLES=off -DMSIX_TESTS=off \ + -DCMAKE_OSX_ARCHITECTURES="$(uname -m)" -DCMAKE_TOOLCHAIN_FILE=../cmake/macos.cmake \ + -DUSE_MSIX_SDK_ZLIB=on -DMACOS=on -DCMAKE_CXX_FLAGS="-I$ICU_PREFIX/include" .. + echo "④ 編(約 5-10 分鐘)" + make -j"$(sysctl -n hw.ncpu)" + echo "✅ makemsix 完成:$MAKEMSIX" + exit 0 +fi + +[[ -x "$MAKEMSIX" ]] || { echo "❌ 找不到 makemsix。先跑一次:bash build-msix.sh --setup" >&2; exit 1; } + +# ── 版本號 ──────────────────────────────────────────────────────────────── +# ⚠️ Store 規定第四段固定 0(保留給 Store 用),前三段各 0-65535、第一段不可為 0。 +VERSION_RAW="${VERSION:-$(git describe --tags --always 2>/dev/null || echo 1.0.0)}" +VERSION_NUM="$(echo "$VERSION_RAW" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)" +[[ -n "$VERSION_NUM" ]] || VERSION_NUM="1.0.0" +MSIX_VERSION="${VERSION_NUM}.0" +# ⚠️ 變數後緊接全形括號必須用 ${} 包起來——全形字元會被 bash 當成變數名的一部分。 +echo "🏷 msix 版本:${MSIX_VERSION}(來源 ${VERSION_RAW})" + +# Identity 三值——**必須與 Partner Center 完全一致**,故從環境變數帶入,預設佔位。 +IDENTITY_NAME="${IDENTITY_NAME:-PLACEHOLDER.ArcrunRAG}" +PUBLISHER="${PUBLISHER:-CN=PLACEHOLDER}" +PUBLISHER_DISPLAY="${PUBLISHER_DISPLAY:-PLACEHOLDER}" + +echo "① 編 Windows exe(沿用既有腳本,單一 exe 內嵌 collector)" +bash build-windows.sh >/dev/null +[[ -f dist-windows/arcrun-tray.exe ]] || { echo "❌ exe 沒編出來" >&2; exit 1; } + +OUT="dist-msix" +rm -rf "$OUT" && mkdir -p "$OUT/root/Assets" +cp dist-windows/arcrun-tray.exe "$OUT/root/" + +echo "② 複製 Store 圖示(品牌素材,2026-07-31 換成 leo 的 CIS)" +# 為什麼不用 `sips -z` 現產:`sips -z 150 310` 會**把方形 icon 橫向拉扁**成 310×150 +#(實測產出的 `a>>` 明顯變形)——CIS 的 Don't 明講「never skew or stretch」。 +# ⇒ 改成預先產好、隨 repo 版控的 assets/store/: +# 方形磚=方形 icon 直接等比縮;寬磚 310×150=**橫式 lockup**(arc >>run)置中於墨底。 +# 產生方式與規範見 assets/store/README.md(可重跑)。 +cp assets/store/*.png "$OUT/root/Assets/" + +echo "③ 寫 AppxManifest.xml(能力宣告誠實且最小化)" +cat > "$OUT/root/AppxManifest.xml" < + + + + Arcrun RAG + $PUBLISHER_DISPLAY + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + +EOF + +echo "④ 打包 msix" +"$MAKEMSIX" pack -d "$OUT/root" -p "$OUT/ArcrunRAG.msix" >/dev/null + +echo "⑤ 回讀驗證(unpack 會驗 blockmap 雜湊,能過=結構合法)" +"$MAKEMSIX" unpack -p "$OUT/ArcrunRAG.msix" -d "$OUT/verify" -ss >/dev/null +a="$(shasum < dist-windows/arcrun-tray.exe)"; b="$(shasum < "$OUT/verify/arcrun-tray.exe")" +[[ "$a" == "$b" ]] || { echo "❌ 回讀的 exe 與原檔不一致" >&2; exit 1; } +rm -rf "$OUT/verify" "$OUT/root" + +echo +echo "✅ 完成:$OUT/ArcrunRAG.msix($(du -h "$OUT/ArcrunRAG.msix" | cut -f1),未簽章=送 Store 正確狀態)" +if [[ "$IDENTITY_NAME" == PLACEHOLDER* ]]; then + echo + echo "⚠️ 現在是**佔位身分**,這顆還不能送審。leo 在 Partner Center 保留名稱後,照這樣重跑:" + echo " IDENTITY_NAME='' PUBLISHER='' \\" + echo " PUBLISHER_DISPLAY='<發行者顯示名稱>' bash build-msix.sh" + echo " 三個值抄自 Partner Center →〔產品〕→〔產品管理〕→〔產品識別資料〕" +fi +echo +echo "⛔ 這台機器驗不到、必須真 Windows 的部分:實際安裝、托盤 icon、選資料夾對話框。" diff --git a/cmd/arcrun-tray/icon.png b/cmd/arcrun-tray/icon.png index ec864e0..f7c9937 100644 Binary files a/cmd/arcrun-tray/icon.png and b/cmd/arcrun-tray/icon.png differ diff --git a/cmd/arcrun-tray/main.go b/cmd/arcrun-tray/main.go index faec4dd..2f0bca7 100644 --- a/cmd/arcrun-tray/main.go +++ b/cmd/arcrun-tray/main.go @@ -34,7 +34,6 @@ import ( "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/driver/desktop" - "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) @@ -1013,7 +1012,7 @@ func main() { } if hasTray { - desk.SetSystemTrayIcon(theme.StorageIcon()) // TODO 換品牌 icon(leo logo 進行中);Mac 建議 template 單色 + desk.SetSystemTrayIcon(trayIcon()) // 品牌 icon(見 trayicon.go:mac 走 template 單色、Windows 走墨底完整版) } rebuildTray() // 開機即建初始選單(否則第一次狀態回呼前選單是空的) // 托盤唯一指示器(leo 07-24 裁決):NSApp 起來後把 activation policy 切 Accessory diff --git a/cmd/arcrun-tray/trayicon.go b/cmd/arcrun-tray/trayicon.go new file mode 100644 index 0000000..81f2f98 --- /dev/null +++ b/cmd/arcrun-tray/trayicon.go @@ -0,0 +1,44 @@ +package main + +import ( + _ "embed" + "runtime" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" +) + +// 托盤品牌 icon(2026-07-31,接 leo 的 CIS;結掉 main.go 舊 TODO「換品牌 icon」)。 +// +// 兩平台要的東西不一樣,所以嵌兩份、開機時選一份: +// +// - macOS 選單列=**template icon**(純黑+alpha),系統依淺色/深色選單列自動反色。 +// 直接塞墨底彩色方塊在選單列會變成「一顆黑方塊」,非常醜——這是 CIS 也講明的: +// ≤26px 的情境降級成 chevron,不用完整的 `a>>` 方塊(字在 22pt 下只剩 ~4px 高,糊掉)。 +// - Windows 系統匣=完整墨底 icon(系統匣背景不固定,template 那套是 mac 專有)。 +// +// ⚠️ fyne v2.5.3 的判斷點(internal/driver/glfw/driver_desktop.go:169): +// **只有 `*theme.ThemedResource` 才會走 `systray.SetTemplateIcon`**, +// 傳一般的 `StaticResource` 會走 `SetIcon`=不反色。所以 mac 這條一定要包 ThemedResource, +// 不能只是「給一張黑白圖」就以為會自動反色。 +// +// 用 go:embed 內嵌,**不讀外部檔案路徑**——打包成 .app/.msix 後工作目錄不是原始碼目錄, +// 靠相對路徑找圖必 404(icon 不見)。 +var ( + //go:embed assets/tray-template.png + trayTemplatePNG []byte + + //go:embed assets/tray-windows.png + trayWindowsPNG []byte +) + +// trayIcon 回傳當前平台該用的托盤 icon。 +func trayIcon() fyne.Resource { + if runtime.GOOS == "windows" { + return fyne.NewStaticResource("arcrun-tray.png", trayWindowsPNG) + } + // mac(與 Linux 一併走這條:深色 panel 下同樣需要單色可反轉的圖) + return theme.NewThemedResource( + fyne.NewStaticResource("arcrun-tray-template.png", trayTemplatePNG), + ) +}