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 {