Files
picoclaw/web/backend/utils/runtime.go
T
肆月 2861fd90ab 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.
2026-06-11 15:10:56 +08:00

160 lines
3.7 KiB
Go

package utils
import (
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/logger"
)
// GetPicoclawHome returns the picoclaw home directory.
// Priority: $PICOCLAW_HOME > ~/.picoclaw
func GetPicoclawHome() string {
return config.GetHome()
}
// GetDefaultConfigPath returns the default path to the picoclaw config file.
func GetDefaultConfigPath() string {
if configPath := os.Getenv(config.EnvConfig); configPath != "" {
return configPath
}
return filepath.Join(GetPicoclawHome(), "config.json")
}
// FindPicoclawBinary locates the picoclaw executable.
// Search order:
// 1. PICOCLAW_BINARY environment variable (explicit override)
// 2. Same directory as the current executable
// 3. Falls back to "picoclaw" and relies on $PATH
func FindPicoclawBinary() string {
binaryName := "picoclaw"
if runtime.GOOS == "windows" {
binaryName = "picoclaw.exe"
}
if p := os.Getenv(config.EnvBinary); p != "" {
if info, _ := os.Stat(p); info != nil && !info.IsDir() {
return p
}
}
if exe, err := os.Executable(); err == nil {
logger.Debugf("Trying to find picoclaw binary in %s", exe)
candidate := filepath.Join(filepath.Dir(exe), binaryName)
if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
return candidate
}
}
return "picoclaw"
}
func appendUniqueIP(addrs []string, seen map[string]struct{}, value string) []string {
value = strings.TrimSpace(value)
if value == "" {
return addrs
}
if _, ok := seen[value]; ok {
return addrs
}
seen[value] = struct{}{}
return append(addrs, value)
}
// GetLocalIPv4s returns all non-loopback local IPv4 addresses.
func GetLocalIPv4s() []string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil
}
results := make([]string, 0, 4)
seen := make(map[string]struct{}, 4)
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if !ok || ipnet.IP == nil || ipnet.IP.IsLoopback() {
continue
}
if ip4 := ipnet.IP.To4(); ip4 != nil {
results = appendUniqueIP(results, seen, ip4.String())
}
}
return results
}
func isDisplayGlobalIPv6(ip net.IP) bool {
if ip == nil || ip.IsLoopback() || ip.To4() != nil {
return false
}
ip = ip.To16()
if ip == nil {
return false
}
// Only show IPv6 global unicast addresses in 2000::/3.
return ip[0]&0xe0 == 0x20
}
// GetGlobalIPv6s returns all IPv6 global unicast addresses.
func GetGlobalIPv6s() []string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return nil
}
results := make([]string, 0, 4)
seen := make(map[string]struct{}, 4)
for _, a := range addrs {
ipnet, ok := a.(*net.IPNet)
if !ok || ipnet.IP == nil {
continue
}
ip := ipnet.IP
if !isDisplayGlobalIPv6(ip) {
continue
}
results = appendUniqueIP(results, seen, ip.String())
}
return results
}
// GetLocalIPv4 returns the first non-loopback local IPv4 address.
func GetLocalIPv4() string {
addrs := GetLocalIPv4s()
if len(addrs) == 0 {
return ""
}
return addrs[0]
}
// GetLocalIPv6 returns the first IPv6 global unicast address.
func GetLocalIPv6() string {
addrs := GetGlobalIPv6s()
if len(addrs) == 0 {
return ""
}
return addrs[0]
}
// GetLocalIP returns a non-loopback local IPv4 address for backward compatibility.
func GetLocalIP() string {
return GetLocalIPv4()
}
// OpenBrowser automatically opens the given URL in the default browser.
func OpenBrowser(url string) error {
switch runtime.GOOS {
case "linux":
return exec.Command("xdg-open", url).Start()
case "windows":
return LauncherExecCommand("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
return exec.Command("open", url).Start()
default:
return fmt.Errorf("unsupported platform")
}
}