From 3355a096b81e99fccd732e03443b5e19ca32f4f1 Mon Sep 17 00:00:00 2001 From: jim800121chen Date: Sun, 12 Apr 2026 08:03:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(local-tool):=20=E9=A6=96=E6=AC=A1=E5=95=9F?= =?UTF-8?q?=E5=8B=95=E8=87=AA=E5=8B=95=E5=AE=89=E8=A3=9D=20Kneron=20WinUSB?= =?UTF-8?q?=20driver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 行為改變:Wails app 首次啟動會在 venv 就緒後、spawn server 前自動呼叫 libwdi 安裝 Kneron WinUSB driver。使用者不再需要手動點「安裝 USB Driver」 按鈕(按鈕保留供失敗後重試用)。 實作: - startServer() step 1.5 新增 ensureDriverInstalled() 呼叫 - 用 /.driver-installed 記號檔避免每次啟動都彈 UAC - 失敗不擋 server 啟動,只寫 log,使用者可稍後手動重試 - 新增 app-level log helper appLog() 寫到 /logs/wails.log (Wails Windows 以 windowsgui subsystem build,os.Stderr 指向 null device, 沒有這個檔使用者看不到 startup 期間的 debug 訊息) - 手動 InstallKneronDriver binding 成功時也寫記號檔 使用者移除 .driver-installed 檔就能強制重裝(例如 Windows 更新把 driver 弄壞時)。 Co-Authored-By: Claude Opus 4.6 (1M context) --- local-tool/visiona-local/app.go | 72 ++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/local-tool/visiona-local/app.go b/local-tool/visiona-local/app.go index dede8e2..97c10e3 100644 --- a/local-tool/visiona-local/app.go +++ b/local-tool/visiona-local/app.go @@ -332,7 +332,68 @@ func (a *App) InstallKneronDriver() error { pyBin = resolved } - return installKneronWinUSBDriver(pyBin) + if err := installKneronWinUSBDriver(pyBin); err != nil { + return err + } + // 手動安裝成功後也寫記號檔(之後就不會再自動彈 UAC) + a.markDriverInstalled() + return nil +} + +// appLog 把一行訊息寫到 /logs/wails.log 以及 os.Stderr。 +// Wails Windows app 以 windowsgui subsystem build,os.Stderr 指向 null device, +// 沒有這個檔使用者就看不到 startup 期間的 debug 訊息。 +func (a *App) appLog(format string, args ...interface{}) { + msg := fmt.Sprintf("["+time.Now().Format("15:04:05")+"] "+format, args...) + fmt.Fprintln(os.Stderr, msg) // dev 模式 / macOS / Linux 會看到 + if a.dataDir == "" { + return + } + logsDir := filepath.Join(a.dataDir, "logs") + _ = os.MkdirAll(logsDir, 0o755) + f, err := os.OpenFile(filepath.Join(logsDir, "wails.log"), + os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + return + } + defer f.Close() + fmt.Fprintln(f, msg) +} + +// ensureDriverInstalled 在首次 startup 時自動安裝 Kneron WinUSB driver。 +// +// 用 `/.driver-installed` 記號檔避免每次啟動都彈 UAC: +// - 檔案存在 → 跳過(之前已安裝過,假設仍有效) +// - 檔案不存在 → 呼叫 installKneronWinUSBDriver → 成功後寫記號檔 +// +// 失敗時寫 stderr 但不回 error(讓 startup 流程繼續走,使用者之後可手動重試)。 +// +// 使用者移除 `.driver-installed` 檔就能強制重裝(例如 Windows 更新把 driver 弄壞時)。 +func (a *App) ensureDriverInstalled(pyBin string) error { + if runtime.GOOS != "windows" { + return nil // macOS / Linux 不需要 + } + marker := filepath.Join(a.dataDir, ".driver-installed") + if fileExists(marker) { + a.appLog("driver 記號檔存在,跳過自動安裝:%s", marker) + return nil + } + + a.appLog("首次啟動:自動安裝 Kneron WinUSB driver(會彈出 UAC 提權視窗,請點「是」)") + if err := installKneronWinUSBDriver(pyBin); err != nil { + a.appLog("driver 自動安裝失敗(非致命):%v", err) + return err + } + a.markDriverInstalled() + a.appLog("driver 自動安裝完成,記號檔已建立:%s", marker) + return nil +} + +// markDriverInstalled 寫 `.driver-installed` 記號檔,內容為時間戳供 debug。 +func (a *App) markDriverInstalled() { + marker := filepath.Join(a.dataDir, ".driver-installed") + content := fmt.Sprintf("installed at %s\n", time.Now().Format(time.RFC3339)) + _ = os.WriteFile(marker, []byte(content), 0o644) } // ----------------------------------------------------------------------- @@ -351,6 +412,15 @@ func (a *App) startServer() error { a.pythonModeR = pyMode a.mu.Unlock() + // 1.5. 首次啟動自動安裝 Kneron WinUSB driver(Windows only,macOS/Linux no-op) + // 失敗不擋 server 啟動 —— 使用者之後可手動點「安裝 USB Driver」按鈕重試。 + // 用 .driver-installed 記號檔避免每次都跑。 + if !a.mockMode && pyBin != "" { + if err := a.ensureDriverInstalled(pyBin); err != nil { + fmt.Fprintln(os.Stderr, "[visiona-local] driver auto-install failed (非致命,可於 UI 手動重試):", err) + } + } + // 2. 找 port port, err := pickPort(defaultPreferredPort) if err != nil {