fix(launcher): hide console flashes in all Windows child processes (#3061)

* fix(launcher): hide console flashes in all Windows child processes

PR #2654 only applied HideWindow to child processes in gateway.go (powershell, tasklist, ps). Several other files still use exec.Command directly, causing visible console windows on Windows.

- startup.go: reg query/add/delete for autostart registry

- version.go: picoclaw version subcommand

- runtime.go: rundll32 for browser launch

- onboard.go: picoclaw onboard subcommand

Add launcherExecCommand to the utils package (matching the api package pattern) and replace all bare exec.Command calls on Windows paths.

* refactor: consolidate launcherExecCommand into utils package

Export LauncherExecCommand and ApplyLauncherProcAttrs from the utils
package as the single source of truth. The api package now imports
and delegates to these exported functions, eliminating code duplication.

Addresses review feedback from imguoguo on PR #3061.
This commit is contained in:
肆月
2026-06-11 15:10:56 +08:00
committed by GitHub
parent 40fe1b0a2d
commit 2861fd90ab
8 changed files with 71 additions and 21 deletions
+9 -3
View File
@@ -2,10 +2,16 @@
package api
import "os/exec"
import (
"os/exec"
"github.com/sipeed/picoclaw/web/backend/utils"
)
func launcherExecCommand(name string, args ...string) *exec.Cmd {
return exec.Command(name, args...)
return utils.LauncherExecCommand(name, args...)
}
func applyLauncherProcAttrs(_ *exec.Cmd) {}
func applyLauncherProcAttrs(cmd *exec.Cmd) {
utils.ApplyLauncherProcAttrs(cmd)
}
+4 -11
View File
@@ -4,21 +4,14 @@ package api
import (
"os/exec"
"syscall"
"github.com/sipeed/picoclaw/web/backend/utils"
)
func launcherExecCommand(name string, args ...string) *exec.Cmd {
cmd := exec.Command(name, args...)
applyLauncherProcAttrs(cmd)
return cmd
return utils.LauncherExecCommand(name, args...)
}
func applyLauncherProcAttrs(cmd *exec.Cmd) {
if cmd == nil {
return
}
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
cmd.SysProcAttr.HideWindow = true
utils.ApplyLauncherProcAttrs(cmd)
}
+11 -3
View File
@@ -277,7 +277,11 @@ func windowsCommandLine(exePath string, args []string) string {
}
func windowsRunKeyExists() (bool, error) {
cmd := exec.Command("reg", "query", `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, "/v", autoStartEntryName)
cmd := launcherExecCommand(
"reg", "query",
`HKCU\Software\Microsoft\Windows\CurrentVersion\Run`,
"/v", autoStartEntryName,
)
if err := cmd.Run(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
@@ -292,11 +296,15 @@ func setWindowsAutoStart(enabled bool, exePath string, args []string) error {
key := `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`
if enabled {
commandLine := windowsCommandLine(exePath, args)
cmd := exec.Command("reg", "add", key, "/v", autoStartEntryName, "/t", "REG_SZ", "/d", commandLine, "/f")
cmd := launcherExecCommand(
"reg", "add", key,
"/v", autoStartEntryName,
"/t", "REG_SZ", "/d", commandLine, "/f",
)
return cmd.Run()
}
cmd := exec.Command("reg", "delete", key, "/v", autoStartEntryName, "/f")
cmd := launcherExecCommand("reg", "delete", key, "/v", autoStartEntryName, "/f")
if err := cmd.Run(); err != nil {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
+3 -1
View File
@@ -259,7 +259,9 @@ func (c *systemVersionCache) resetForTest() {
// executePicoclawVersion runs the version subcommand against the
// discovered picoclaw executable.
func executePicoclawVersion(ctx context.Context, execPath string) (string, error) {
out, err := exec.CommandContext(ctx, execPath, "version").CombinedOutput()
cmd := exec.CommandContext(ctx, execPath, "version")
applyLauncherProcAttrs(cmd)
out, err := cmd.CombinedOutput()
if err == nil {
return string(out), nil
}
+14
View File
@@ -0,0 +1,14 @@
//go:build !windows
package utils
import "os/exec"
// LauncherExecCommand creates an exec.Cmd. On non-Windows platforms, this is
// a simple wrapper around exec.Command.
func LauncherExecCommand(name string, args ...string) *exec.Cmd {
return exec.Command(name, args...)
}
// ApplyLauncherProcAttrs is a no-op on non-Windows platforms.
func ApplyLauncherProcAttrs(_ *exec.Cmd) {}
+28
View File
@@ -0,0 +1,28 @@
//go:build windows
package utils
import (
"os/exec"
"syscall"
)
// LauncherExecCommand creates an exec.Cmd with the HideWindow attribute set
// to prevent console window flashes on Windows.
func LauncherExecCommand(name string, args ...string) *exec.Cmd {
cmd := exec.Command(name, args...)
ApplyLauncherProcAttrs(cmd)
return cmd
}
// ApplyLauncherProcAttrs applies Windows-specific process attributes to hide
// the console window. It is safe to call with a nil cmd.
func ApplyLauncherProcAttrs(cmd *exec.Cmd) {
if cmd == nil {
return
}
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
cmd.SysProcAttr.HideWindow = true
}
+1 -2
View File
@@ -3,13 +3,12 @@ package utils
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/sipeed/picoclaw/pkg/config"
)
var execCommand = exec.Command
var execCommand = LauncherExecCommand
func EnsureOnboarded(configPath string) error {
_, err := os.Stat(configPath)
+1 -1
View File
@@ -150,7 +150,7 @@ func OpenBrowser(url string) error {
case "linux":
return exec.Command("xdg-open", url).Start()
case "windows":
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
return LauncherExecCommand("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
return exec.Command("open", url).Start()
default: