From d80e9e1357f17a609ad564f306b718d0ae4b546d Mon Sep 17 00:00:00 2001 From: richblack Date: Wed, 5 Aug 2026 15:50:57 +0800 Subject: [PATCH] =?UTF-8?q?t196=EF=BC=9AWindows=20=E7=89=88=E9=87=8D?= =?UTF-8?q?=E7=B7=A8=EF=BC=88Wails=EF=BC=89=EF=BC=8BMSIX=20=E6=89=93?= =?UTF-8?q?=E5=8C=85=E2=80=94=E2=80=94=E5=9C=A8=20Mac=20=E4=B8=8A=E4=BA=A4?= =?UTF-8?q?=E5=8F=89=E7=B7=A8=E8=AD=AF=EF=BC=8C=E4=B8=8D=E9=9C=80=20Window?= =?UTF-8?q?s=20=E6=A9=9F=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit leo 08-05:「Windows 版一定要重編,因為我只有 Mac,我需要人家測試, 要做到最完善不要給人家找麻煩,然後當測試者給我截圖安裝成功就要提交 ms store」 ## 缺口 Windows 打包腳本全在**舊的 arcrun-tray(fyne 版)**, 新的 arcrun-app(Wails,t193/t194 整批 UI 重做)**完全沒有 Windows 產線** ⇒ Windows 封測者只能拿到 v0.15.7 的舊 fyne 版。 ## 新增 · tray_windows.go — Windows 系統匣(energye/systray) 行為與 macOS 版逐項對齊:左鍵開視窗/右鍵只有「結束 Arcrun」/關窗不結束。 ⚠️ 為什麼 Windows 能用 systray 而 macOS 不能(wiki 有全文): macOS 上它在 Wails 之下根本不建托盤(setInternalLoop 只在 systray.Run() 呼叫), 改用 systray.Run() 又搶 macOS 主 loop ⇒ macOS 最後走原生 NSStatusItem。 **Windows 走 Win32 訊息迴圈,沒有主執行緒限制**,所以這裡用 systray 是對的, 不是抄 macOS 失敗的做法。 trayApp 與 ICO 都在本檔自帶(tray_darwin.go 有 //go:build darwin,Windows 看不到)。 · build-win.sh — 交叉編譯 zip(CGO_ENABLED=1 + mingw-w64,Wails Windows 要 WebView2 綁定) **同綑 collector.exe**:daemon 本體是 collector direct,沒同綑=裝了不會同步 (t194 在 Mac 版就漏過一次)。supervise.go 的 collectorBinPath() 已處理 .exe 副檔名。 · build-msix.sh — MS Store 用的 msix(沿用 makemsix,Mac 上就打得出來) 與舊 fyne 版差別:**Wails 是兩個 exe**,兩個都要進 msix root。 Identity 三值走環境變數(要與 Partner Center 完全一致),預設佔位並印警告。 ## 驗 · Windows zip:兩個檔都是正牌 PE(MZ 標頭)/Arcrun.exe 11.4MB+collector.exe 16.1MB · 退避修復確實編進 Windows 版(不剝符號重編一份,grep 到 MarkFailed/ShouldRetry/ retrySkipReason 各 2 處;出貨版用 -s -w 剝符號故查不到,屬預期) · MSIX:16 檔、含 AppxBlockMap;zip 回讀兩個 exe 的 sha256 與原檔**逐一相符**; manifest Executable="Arcrun.exe"、Version=0.18.4.0 · **macOS 版不受影響**:編譯通過、三支機械閘全過 ## 兩個踩到的坑(已修) · zip 用相對路徑在 cd 後失效(zip I/O error,fallback 湊巧成功但不可靠)⇒ 改絕對路徑 · 全形括號緊接變數會被當成變數名(腳本自己的註解就警告過)⇒ 改 ${VAR} ## 殘項 · Identity 仍是 PLACEHOLDER ⇒ **這顆 msix 還不能送 Store**,等 leo 給 Partner Center 三值 · 未在真 Windows 上跑過(誠實界定:只證明編得出來,不證明跑起來對) --- cmd/arcrun-app/.gitignore | 5 ++ cmd/arcrun-app/build-msix.sh | 118 +++++++++++++++++++++++++++++++++ cmd/arcrun-app/build-win.sh | 62 +++++++++++++++++ cmd/arcrun-app/go.mod | 5 +- cmd/arcrun-app/go.sum | 2 + cmd/arcrun-app/tray_windows.go | 72 ++++++++++++++++++++ 6 files changed, 263 insertions(+), 1 deletion(-) create mode 100755 cmd/arcrun-app/build-msix.sh create mode 100755 cmd/arcrun-app/build-win.sh create mode 100644 cmd/arcrun-app/tray_windows.go diff --git a/cmd/arcrun-app/.gitignore b/cmd/arcrun-app/.gitignore index 129d522..3b1ce82 100644 --- a/cmd/arcrun-app/.gitignore +++ b/cmd/arcrun-app/.gitignore @@ -1,3 +1,8 @@ build/bin node_modules frontend/dist + +# Windows 打包中間產物(t196):exe 與 msix 解開後的 root/ 都是可重現的產物, +# 每次重編都變動、且是二進位 ⇒ 不進版控(產物走 bundles repo 出貨)。 +arcrun-collector.exe +dist-msix/root/ diff --git a/cmd/arcrun-app/build-msix.sh b/cmd/arcrun-app/build-msix.sh new file mode 100755 index 0000000..c1f7724 --- /dev/null +++ b/cmd/arcrun-app/build-msix.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# build-msix.sh — 把 Wails 版 Arcrun 打包成 Microsoft Store 用的 .msix(t196/2026-08-05) +# +# leo 08-05:「當測試者給我截圖安裝成功就要提交 ms store」 +# +# 🎯 **在 Mac 上就打得出 msix**,不需要 Windows 機器、不需要 MakeAppx。 +# 靠微軟自家開源跨平台工具 msix-packaging 的 `makemsix`。 +# +# ⚠️ **不必自購簽章憑證**:送 Store 的 msix 不用簽——微軟過審後會用自己的憑證重簽。 +# 但**側載**(不走 Store)就必須自己簽。(官方說明見 ../arcrun-tray/docs/store-submission.md §2) +# +# 與舊 fyne 版(../arcrun-tray/build-msix.sh)的差別: +# 舊版是單一 exe(collector 用 //go:embed 吃進去); +# **Wails 版是兩個 exe**(Arcrun.exe + arcrun-collector.exe,見 supervise.go 的 +# os.Executable() 同層查找)⇒ 兩個都要進 msix root,缺一個就「裝了不會同步」。 +# +# 前置(一次就好): +# brew install mingw-w64 cmake icu4c +# bash ../arcrun-tray/build-msix.sh --setup # 建 makemsix(約 5-10 分鐘) +# +# 用法: +# IDENTITY_NAME= \ +# PUBLISHER= \ +# PUBLISHER_DISPLAY=<顯示名> \ +# bash build-msix.sh +set -euo pipefail +cd "$(dirname "$0")" + +MSIX_SDK_DIR="${MSIX_SDK_DIR:-$HOME/.cache/msix-packaging}" +MAKEMSIX="$MSIX_SDK_DIR/.vs/bin/makemsix" +[[ -x "$MAKEMSIX" ]] || { + echo "❌ 找不到 makemsix:$MAKEMSIX" + echo " 先跑一次:bash ../arcrun-tray/build-msix.sh --setup" + exit 1 +} + +VERSION_RAW="${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo dev)}" +# msix 版本必須是四段數字 a.b.c.d,且**最後一段必須是 0**(Store 規定,保留給微軟)。 +MSIX_VERSION="$(echo "$VERSION_RAW" | sed 's/^v//' | awk -F. '{printf "%d.%d.%d.0", $1, $2, $3}')" +# ⚠️ 變數後緊接全形括號必須用 ${} 包起來——全形字元會被 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}" +if [[ "$IDENTITY_NAME" == PLACEHOLDER* ]]; then + echo "⚠️ Identity 仍是佔位值——**這顆 msix 不能送 Store**,只能拿來驗打包流程。" + echo " 送審前要帶:IDENTITY_NAME / PUBLISHER / PUBLISHER_DISPLAY(三值從 Partner Center 抄)" +fi + +echo "① 編 Windows 版(兩個 exe)" +VERSION="$VERSION_RAW" bash build-win.sh >/dev/null +[[ -f build/bin/arcrun-app.exe ]] || { echo "❌ Arcrun.exe 沒編出來" >&2; exit 1; } +[[ -f arcrun-collector.exe ]] || { echo "❌ collector.exe 沒編出來" >&2; exit 1; } + +OUT="dist-msix" +rm -rf "$OUT" && mkdir -p "$OUT/root/Assets" +cp build/bin/arcrun-app.exe "$OUT/root/Arcrun.exe" +cp arcrun-collector.exe "$OUT/root/arcrun-collector.exe" + +echo "② 複製 Store 圖示(沿用 CIS 版,不重產——sips 縮放會把方形拉扁)" +cp ../arcrun-tray/assets/store/*.png "$OUT/root/Assets/" + +echo "③ 寫 AppxManifest.xml(能力宣告誠實且最小化)" +cat > "$OUT/root/AppxManifest.xml" < + + + + Arcrun + $PUBLISHER_DISPLAY + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + +EOF + +echo "④ 打包 msix" +"$MAKEMSIX" pack -d "$OUT/root" -p "$OUT/Arcrun.msix" >/dev/null + +SIZE=$(ls -lh "$OUT/Arcrun.msix" | awk '{print $5}') +echo "✅ 完成:$OUT/Arcrun.msix(${SIZE},未簽章=送 Store 的正確狀態)" +echo +echo "🔬 送審前必做(否則會被退件):" +echo " · Identity 三值要與 Partner Center 完全一致(現在:${IDENTITY_NAME})" +echo " · broadFileSystemAccess 是受限能力,送審要寫明用途" +echo " (本 App 需要它才能讀使用者自選的任意資料夾)" diff --git a/cmd/arcrun-app/build-win.sh b/cmd/arcrun-app/build-win.sh new file mode 100755 index 0000000..c9306b2 --- /dev/null +++ b/cmd/arcrun-app/build-win.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# build-win.sh — 打包 Arcrun Windows 版(Wails,t196/2026-08-05) +# +# leo 08-05:「Windows 版一定要重編,因為我只有 Mac,我需要人家測試, +# 要做到最完善不要給人家找麻煩」 +# +# 🎯 **在 Mac 上就能交叉編譯**,不需要 Windows 機器/Docker。 +# 條件只有一個:`brew install mingw-w64`(提供 x86_64-w64-mingw32-gcc)。 +# Wails 的 Windows 版要 CGo(WebView2 綁定),所以 CGO_ENABLED=1 + mingw。 +# +# 與 build-mac.sh 同一個要點:**必須把 collector 同綑進去**。 +# daemon 的本體是 `collector direct`(監看/萃卡/上傳),App 只是門面; +# 沒同綑 ⇒ 使用者裝了也不會同步(t194 在 Mac 版就漏過一次)。 +# +# ⚠️ 誠實界定(沿用 build-windows.sh 的自陳): +# 本腳本只證明「**編得出來**」,不證明「**跑起來對**」。 +# 托盤圖示會不會出現、WebView2 有沒有裝、SmartScreen 攔截行為, +# **只有真的在 Windows 上跑才驗得到**——那一步歸 leo/封測者真機。 +set -euo pipefail +cd "$(dirname "$0")" +export PATH="$PATH:$(go env GOPATH)/bin" + +VERSION="${VERSION:-$(git describe --tags --always --dirty 2>/dev/null || echo dev)}" +BUILD_TIME="$(date '+%Y%m%d-%H%M')" +echo "🏷 版本:${VERSION}(build ${BUILD_TIME})" + +command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1 || { + echo "❌ 缺 mingw-w64(Wails Windows 版需要 CGo 交叉編譯器)" + echo " 裝一次就好:brew install mingw-w64" + exit 1 +} + +echo "① 編 collector.exe(同綑執行檔,純 stdlib、CGO_ENABLED=0)" +( cd ../.. && GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \ + go build -ldflags "-s -w" -o "cmd/arcrun-app/arcrun-collector.exe" . ) + +echo "② wails build(windows/amd64,CGo 走 mingw)" +CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc \ + wails build -platform windows/amd64 -clean \ + -ldflags "-X main.version=${VERSION} -X main.buildTime=${BUILD_TIME}" + +OUT="dist"; mkdir -p "$OUT" +STAGE="$(mktemp -d)/Arcrun" +mkdir -p "$STAGE" + +echo "③ 同綑 collector.exe(App 用 os.Executable() 找同層,見 supervise.go)" +cp build/bin/arcrun-app.exe "$STAGE/Arcrun.exe" +cp arcrun-collector.exe "$STAGE/arcrun-collector.exe" + +echo "④ 打包 zip" +# 用絕對路徑:下面會 cd 進暫存目錄打包,相對路徑會找不到(實撞:zip I/O error)。 +ZIP="$(cd "$OUT" && pwd)/ArcrunRAG-win-unsigned-${VERSION}.zip" +rm -f "$ZIP" +( cd "$(dirname "$STAGE")" && zip -qr "$ZIP" "Arcrun" ) +rm -rf "$(dirname "$STAGE")" + +SIZE=$(ls -lh "$ZIP" | awk '{print $5}') +echo "✅ 完成:${ZIP}(${SIZE})" +echo +echo "🔬 使用者流程:解壓縮 → 執行 Arcrun.exe" +echo " ⚠️ 未簽章 ⇒ SmartScreen 會攔(「更多資訊」→「仍要執行」)。" +echo " 要免除這一步=上 MS Store(見 build-msix.sh)或買 EV 憑證。" diff --git a/cmd/arcrun-app/go.mod b/cmd/arcrun-app/go.mod index a297cf3..07a1050 100644 --- a/cmd/arcrun-app/go.mod +++ b/cmd/arcrun-app/go.mod @@ -2,7 +2,10 @@ module arcrun-app go 1.25.0 -require github.com/wailsapp/wails/v2 v2.13.0 +require ( + github.com/energye/systray v1.0.3 + github.com/wailsapp/wails/v2 v2.13.0 +) require ( git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect diff --git a/cmd/arcrun-app/go.sum b/cmd/arcrun-app/go.sum index 1d25d78..955bc0e 100644 --- a/cmd/arcrun-app/go.sum +++ b/cmd/arcrun-app/go.sum @@ -4,6 +4,8 @@ github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/energye/systray v1.0.3 h1:XnyjJCeRU5z00bpNOic2fGTKz/7yHZMZjWiGIVXDS+4= +github.com/energye/systray v1.0.3/go.mod h1:HelKhC3PXwv3ryDxbuQqV+7kAxAYNzE5cfdrerGOZTc= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= diff --git a/cmd/arcrun-app/tray_windows.go b/cmd/arcrun-app/tray_windows.go new file mode 100644 index 0000000..6bd4b50 --- /dev/null +++ b/cmd/arcrun-app/tray_windows.go @@ -0,0 +1,72 @@ +//go:build windows + +package main + +// tray_windows.go — Windows 系統匣(t196,2026-08-05) +// +// 為什麼 Windows 用 energye/systray 而 macOS 不用(wiki mistakes 有全文): +// macOS 上 systray 在 Wails 之下**根本不會建立托盤**——`setInternalLoop(true)` 只在 +// `systray.Run()` 裡呼叫,走 `RunWithExternalLoop` 時 `registerSystray()` 第一行就 return; +// 而改用 `systray.Run()` 又會與 Wails 搶 macOS 主 loop(SIGTRAP)。 +// ⇒ macOS 最後改走原生 `NSStatusItem`(tray_darwin.m)。 +// +// **Windows 沒有這個限制**:托盤走 Win32 訊息迴圈(自己的 goroutine 就能跑), +// 不像 macOS 的 NSStatusItem 硬性要求主執行緒。所以這裡用 systray 是合適的, +// 不是「抄 macOS 失敗的做法」。 +// +// 行為與 macOS 版**逐項對齊**(tray_darwin.go/.m): +// · 左鍵點 icon → 直接開視窗(不彈選單) +// · 右鍵 → 只有「結束 Arcrun」一項 +// · 關視窗 → 隱藏不結束(daemon 常駐;在 app.go 的 OnBeforeClose 處理) + +import ( + _ "embed" + + "github.com/energye/systray" +) + +// trayApp 讓回呼找得到 App(與 tray_darwin.go 同名同用途; +// 該檔有 //go:build darwin,Windows 編譯時不存在,故這裡自帶一份)。 +var trayApp *App + +// Windows 系統匣要 .ico(PNG 會顯示成空白/方塊)。 +// build/windows/icon.ico 是 Wails 產生 exe 圖示用的同一顆,一併拿來當托盤圖。 +// +//go:embed build/windows/icon.ico +var trayIconICO []byte + +// setupTray 建立系統匣圖示。 +// +// 與 macOS 版的介面契約相同(main.go 只呼叫 setupTray(app)), +// 但這裡**不阻塞**:systray 自己開 goroutine 跑訊息迴圈,Wails 主迴圈不受影響。 +func setupTray(app *App) { + trayApp = app + // RunWithExternalLoop 回傳 start/end;Windows 下 start 不阻塞。 + trayStart, trayEnd = systray.RunWithExternalLoop(func() { + systray.SetIcon(trayIconICO) + systray.SetTitle("Arcrun") + systray.SetTooltip("Arcrun — 你的知識庫同步小幫手") + + // 🔴 右鍵選單:**只有結束**(leo 08-05:「依照 google drive,托盤裡只剩下 + // 按右鍵會結束,其他設定都在這個頁面」)。 + quit := systray.AddMenuItem("結束 Arcrun", "結束 Arcrun(同步會停止)") + quit.Click(func() { + if trayApp != nil { + trayApp.Quit() + } + }) + + // 左鍵=直接開視窗,不彈選單(Google Drive 式)。 + systray.SetOnClick(func(menu systray.IMenu) { + if trayApp != nil { + trayApp.ShowWindow() + } + }) + // 右鍵才顯示選單。 + systray.SetOnRClick(func(menu systray.IMenu) { + if menu != nil { + _ = menu.ShowMenu() + } + }) + }, func() {}) +}