fix(windows): GUI subsystem + parent-console attach + canonical muyue.exe (v0.7.5)
All checks were successful
PR Check / check (pull_request) Successful in 58s
All checks were successful
PR Check / check (pull_request) Successful in 58s
Three Windows install/launch issues reported by the user:
1. Double-click on Desktop shortcut → dialog "This is a command line
tool. You need to open cmd.exe and run it from there."
Cause: charmbracelet/huh detects no TTY when launched via Explorer
and aborts. Fix:
- cmd/muyue/commands/root.go: skip RunFirstTimeSetup when
os.Stdin is not a character device; persist config.Default()
and let the React onboarding wizard handle first-run UX.
- ci-{main,develop}.yml: build Windows binaries with
-ldflags="-H=windowsgui" so the .exe is a GUI subsystem app —
no console window flashes on double-click.
2. CLI sub-commands (`muyue scan`, `muyue install-shortcuts`, etc.)
would lose all output under -H=windowsgui when launched from
cmd.exe / PowerShell. Mitigation:
- cmd/muyue/console_windows.go (new, build-tagged): on init(),
call kernel32!AttachConsole(ATTACH_PARENT_PROCESS). If the
parent has a console, rebind os.Stdout/os.Stderr/os.Stdin to
it and call log.SetOutput(os.Stderr) so existing log.Printf
calls surface. If no parent console (Explorer), exit silently.
3. After install, `muyue` not recognized in PowerShell.
Causes: (a) the extracted binary is muyue-windows-amd64.exe, not
muyue.exe; (b) the user PATH update by install-shortcuts doesn't
propagate to the existing PowerShell session.
Fix in install-shortcuts:
- Copy self to <installDir>/muyue.exe (rename impossible — the
running .exe is locked on Windows) so `muyue` resolves once
PATH is set.
- Update Desktop + Start Menu .lnk to target the canonical
muyue.exe rather than the platform-suffixed binary.
- Print the line `$env:Path += ';<installDir>'` for the user to
paste, refreshing the current session immediately.
- ci-main.yml install snippet bumps to 5 lines, last being
`$env:Path += ";$dest"`.
- internal/version/version.go: 0.7.4 → 0.7.5
- CHANGELOG.md: v0.7.5 entry covers all three fixes
This commit is contained in:
@@ -80,12 +80,13 @@ jobs:
|
|||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
VERSION=${{ steps.version.outputs.version }}
|
VERSION=${{ steps.version.outputs.version }}
|
||||||
LDFLAGS="-s -w -X github.com/muyue/muyue/internal/version.Prerelease=${VERSION#v}"
|
LDFLAGS="-s -w -X github.com/muyue/muyue/internal/version.Prerelease=${VERSION#v}"
|
||||||
|
WIN_LDFLAGS="$LDFLAGS -H=windowsgui"
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$LDFLAGS" -o dist/muyue-linux-amd64 ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$LDFLAGS" -o dist/muyue-linux-amd64 ./cmd/muyue/
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$LDFLAGS" -o dist/muyue-linux-arm64 ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$LDFLAGS" -o dist/muyue-linux-arm64 ./cmd/muyue/
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$LDFLAGS" -o dist/muyue-darwin-amd64 ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$LDFLAGS" -o dist/muyue-darwin-amd64 ./cmd/muyue/
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$LDFLAGS" -o dist/muyue-darwin-arm64 ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$LDFLAGS" -o dist/muyue-darwin-arm64 ./cmd/muyue/
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$LDFLAGS" -o dist/muyue-windows-amd64.exe ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$WIN_LDFLAGS" -o dist/muyue-windows-amd64.exe ./cmd/muyue/
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$LDFLAGS" -o dist/muyue-windows-arm64.exe ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$WIN_LDFLAGS" -o dist/muyue-windows-arm64.exe ./cmd/muyue/
|
||||||
|
|
||||||
- name: Package archives
|
- name: Package archives
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -75,12 +75,17 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
LDFLAGS="-s -w"
|
LDFLAGS="-s -w"
|
||||||
|
# Windows builds use -H=windowsgui so the binary registers as a GUI
|
||||||
|
# subsystem app: double-clicking from the Desktop shortcut does not
|
||||||
|
# spawn a console window (and huh's "This is a command line tool"
|
||||||
|
# banner can never appear).
|
||||||
|
WIN_LDFLAGS="$LDFLAGS -H=windowsgui"
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$LDFLAGS" -o dist/muyue-linux-amd64 ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$LDFLAGS" -o dist/muyue-linux-amd64 ./cmd/muyue/
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$LDFLAGS" -o dist/muyue-linux-arm64 ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$LDFLAGS" -o dist/muyue-linux-arm64 ./cmd/muyue/
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$LDFLAGS" -o dist/muyue-darwin-amd64 ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$LDFLAGS" -o dist/muyue-darwin-amd64 ./cmd/muyue/
|
||||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$LDFLAGS" -o dist/muyue-darwin-arm64 ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$LDFLAGS" -o dist/muyue-darwin-arm64 ./cmd/muyue/
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$LDFLAGS" -o dist/muyue-windows-amd64.exe ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$WIN_LDFLAGS" -o dist/muyue-windows-amd64.exe ./cmd/muyue/
|
||||||
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$LDFLAGS" -o dist/muyue-windows-arm64.exe ./cmd/muyue/
|
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$WIN_LDFLAGS" -o dist/muyue-windows-arm64.exe ./cmd/muyue/
|
||||||
|
|
||||||
- name: Package archives
|
- name: Package archives
|
||||||
run: |
|
run: |
|
||||||
@@ -145,12 +150,13 @@ jobs:
|
|||||||
echo "sudo mv muyue-darwin-arm64 /usr/local/bin/muyue"
|
echo "sudo mv muyue-darwin-arm64 /usr/local/bin/muyue"
|
||||||
echo "\`\`\`"
|
echo "\`\`\`"
|
||||||
echo ""
|
echo ""
|
||||||
echo "**Windows (x86_64)** — sans privilèges admin, crée les raccourcis Bureau + Menu Démarrer :"
|
echo "**Windows (x86_64)** — sans privilèges admin, crée les raccourcis Bureau + Menu Démarrer + commande \`muyue\` dans la session courante :"
|
||||||
echo "\`\`\`powershell"
|
echo "\`\`\`powershell"
|
||||||
echo "\$dest = \"\$env:LOCALAPPDATA\\Muyue\"; New-Item -ItemType Directory -Force -Path \$dest | Out-Null"
|
echo "\$dest = \"\$env:LOCALAPPDATA\\Muyue\"; New-Item -ItemType Directory -Force -Path \$dest | Out-Null"
|
||||||
echo "Invoke-WebRequest -Uri \"${DL_URL}/muyue-windows-amd64.zip\" -OutFile \"\$env:TEMP\\muyue.zip\""
|
echo "Invoke-WebRequest -Uri \"${DL_URL}/muyue-windows-amd64.zip\" -OutFile \"\$env:TEMP\\muyue.zip\""
|
||||||
echo "Expand-Archive -Path \"\$env:TEMP\\muyue.zip\" -DestinationPath \$dest -Force"
|
echo "Expand-Archive -Path \"\$env:TEMP\\muyue.zip\" -DestinationPath \$dest -Force"
|
||||||
echo "& \"\$dest\\muyue-windows-amd64.exe\" install-shortcuts"
|
echo "& \"\$dest\\muyue-windows-amd64.exe\" install-shortcuts"
|
||||||
|
echo "\$env:Path += \";\$dest\""
|
||||||
echo "\`\`\`"
|
echo "\`\`\`"
|
||||||
} > /tmp/stable_changelog.md
|
} > /tmp/stable_changelog.md
|
||||||
echo "path=/tmp/stable_changelog.md" >> $GITHUB_OUTPUT
|
echo "path=/tmp/stable_changelog.md" >> $GITHUB_OUTPUT
|
||||||
|
|||||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
|
|
||||||
|
## v0.7.5
|
||||||
|
|
||||||
|
### Fix Windows : commande `muyue` reconnue après install
|
||||||
|
|
||||||
|
Symptôme rapporté : après les commandes d'install, `muyue` retourne `n'est pas reconnu comme nom d'applet de commande`. Causes :
|
||||||
|
- Le binaire extrait s'appelle `muyue-windows-amd64.exe` — taper `muyue` ne résoud pas
|
||||||
|
- La PATH utilisateur a été mise à jour mais la session PowerShell courante n'en hérite que pour les NOUVEAUX processus
|
||||||
|
|
||||||
|
Corrections dans `install-shortcuts` :
|
||||||
|
- **Copie canonique** : `muyue.exe` est créé à côté de `muyue-windows-amd64.exe` (copy, pas rename — le binaire en cours d'exécution est verrouillé sur Windows). Les raccourcis Bureau / Menu Démarrer ciblent désormais cette copie.
|
||||||
|
- **Hint de session** : la commande imprime `$env:Path += ';...'` à coller pour activer `muyue` dans le shell courant sans rouvrir un terminal.
|
||||||
|
|
||||||
|
Snippet d'install passe à 5 lignes : la dernière (`$env:Path += ";$dest"`) rend la commande dispo immédiatement dans la session.
|
||||||
|
|
||||||
|
### Fix Windows : double-clic du raccourci fonctionne enfin
|
||||||
|
|
||||||
|
Symptôme rapporté : après installation, double-clic sur le raccourci Bureau → boîte de dialogue *"This is a command line tool. You need to open cmd.exe and run it from there."*. Cause : `charmbracelet/huh` (utilisé pour la TUI de premier lancement) détecte l'absence de TTY interactif quand le binaire est lancé via Explorer Windows et avorte avec ce message.
|
||||||
|
|
||||||
|
Double correctif :
|
||||||
|
|
||||||
|
1. **Skip de la TUI sans terminal interactif** (`cmd/muyue/commands/root.go::isInteractiveStdin`) — si `os.Stdin.Stat()` indique pas de `os.ModeCharDevice`, on saute `profiler.RunFirstTimeSetup` et on persiste un `config.Default()`. L'onboarding web (déjà existant) prend ensuite le relais dès l'ouverture du navigateur — aucune régression : avec un vrai terminal, la TUI continue de tourner comme avant.
|
||||||
|
|
||||||
|
2. **Build Windows en GUI subsystem** (`-H=windowsgui` ajouté aux Windows builds dans `ci-main.yml` et `ci-develop.yml`) — le binaire ne demande plus de console, donc plus aucun flash de fenêtre noire au double-clic.
|
||||||
|
|
||||||
|
Conséquence : les sous-commandes CLI (`muyue scan`, `muyue version`, `muyue install-shortcuts`) ne produiraient plus d'output quand lancées depuis cmd.exe. Mitigation : nouveau fichier `cmd/muyue/console_windows.go` qui appelle `kernel32!AttachConsole(ATTACH_PARENT_PROCESS)` au démarrage. Si un terminal parent existe, on s'y rattache et `os.Stdout` / `os.Stderr` / `os.Stdin` y sont rebindés ; sinon, on tourne silencieusement (cas double-clic). Compatible des deux usages sans deux binaires séparés.
|
||||||
|
|
||||||
## v0.7.4
|
## v0.7.4
|
||||||
|
|
||||||
### Logo Muyue intégré
|
### Logo Muyue intégré
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -34,7 +36,20 @@ var installShortcutsCmd = &cobra.Command{
|
|||||||
installDir := filepath.Dir(exe)
|
installDir := filepath.Dir(exe)
|
||||||
|
|
||||||
fmt.Println("Installing Muyue shortcuts...")
|
fmt.Println("Installing Muyue shortcuts...")
|
||||||
fmt.Printf(" Executable : %s\n", exe)
|
fmt.Printf(" Source : %s\n", exe)
|
||||||
|
|
||||||
|
// Provide a clean `muyue.exe` next to the platform-suffixed binary so
|
||||||
|
// users can type `muyue` once the install dir is on PATH. Copy (not
|
||||||
|
// rename) because the running .exe is locked on Windows.
|
||||||
|
canonicalExe := filepath.Join(installDir, "muyue.exe")
|
||||||
|
if !strings.EqualFold(exe, canonicalExe) {
|
||||||
|
if err := copyFile(exe, canonicalExe); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, " Copy : warning — could not create muyue.exe: %v\n", err)
|
||||||
|
canonicalExe = exe
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" Canonical : %s\n", canonicalExe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
desktop, err := userShellFolder("Desktop")
|
desktop, err := userShellFolder("Desktop")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -48,12 +63,12 @@ var installShortcutsCmd = &cobra.Command{
|
|||||||
desktopLnk := filepath.Join(desktop, "Muyue.lnk")
|
desktopLnk := filepath.Join(desktop, "Muyue.lnk")
|
||||||
startLnk := filepath.Join(startMenu, "Muyue.lnk")
|
startLnk := filepath.Join(startMenu, "Muyue.lnk")
|
||||||
|
|
||||||
if err := createWindowsShortcut(desktopLnk, exe, installDir, "Muyue — AI-powered dev environment"); err != nil {
|
if err := createWindowsShortcut(desktopLnk, canonicalExe, installDir, "Muyue — AI-powered dev environment"); err != nil {
|
||||||
return fmt.Errorf("create desktop shortcut: %w", err)
|
return fmt.Errorf("create desktop shortcut: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Printf(" Desktop : %s\n", desktopLnk)
|
fmt.Printf(" Desktop : %s\n", desktopLnk)
|
||||||
|
|
||||||
if err := createWindowsShortcut(startLnk, exe, installDir, "Muyue — AI-powered dev environment"); err != nil {
|
if err := createWindowsShortcut(startLnk, canonicalExe, installDir, "Muyue — AI-powered dev environment"); err != nil {
|
||||||
return fmt.Errorf("create Start Menu shortcut: %w", err)
|
return fmt.Errorf("create Start Menu shortcut: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Printf(" Start Menu : %s\n", startLnk)
|
fmt.Printf(" Start Menu : %s\n", startLnk)
|
||||||
@@ -61,14 +76,37 @@ var installShortcutsCmd = &cobra.Command{
|
|||||||
if err := addUserPATH(installDir); err != nil {
|
if err := addUserPATH(installDir); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, " PATH : warning — could not add %s to user PATH: %v\n", installDir, err)
|
fmt.Fprintf(os.Stderr, " PATH : warning — could not add %s to user PATH: %v\n", installDir, err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" PATH : added %s (open a new terminal to pick it up)\n", installDir)
|
fmt.Printf(" PATH : added %s\n", installDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("\nDone — double-click the Muyue icon on your Desktop to launch.")
|
fmt.Println("\nDone — double-click the Muyue icon on your Desktop to launch.")
|
||||||
|
fmt.Println("\nTo use 'muyue' from this PowerShell session right now, run:")
|
||||||
|
fmt.Printf(" $env:Path += ';%s'\n", installDir)
|
||||||
|
fmt.Println("(New terminals will pick up the user PATH automatically.)")
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copyFile duplicates src to dst, overwriting an existing dst (used to drop a
|
||||||
|
// `muyue.exe` next to the platform-suffixed binary so the command is callable
|
||||||
|
// as `muyue` from PATH).
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
if _, err := io.Copy(out, in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return out.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(installShortcutsCmd)
|
rootCmd.AddCommand(installShortcutsCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,30 +24,61 @@ func Execute() error {
|
|||||||
return rootCmd.Execute()
|
return rootCmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isInteractiveStdin reports whether os.Stdin is connected to a real terminal.
|
||||||
|
// Used to decide between the TUI first-time setup (huh forms) and a no-op
|
||||||
|
// fallback that defers onboarding to the web wizard. Returns false when the
|
||||||
|
// binary is launched by a double-click on Windows (Explorer attaches a pseudo
|
||||||
|
// console without a usable TTY) — which is the exact case where huh prints
|
||||||
|
// "This is a command line tool. You need to open cmd.exe and run it from there."
|
||||||
|
// and exits.
|
||||||
|
func isInteractiveStdin() bool {
|
||||||
|
stat, err := os.Stdin.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (stat.Mode() & os.ModeCharDevice) != 0
|
||||||
|
}
|
||||||
|
|
||||||
func loadOrSetupConfig() *config.MuyueConfig {
|
func loadOrSetupConfig() *config.MuyueConfig {
|
||||||
if !config.Exists() {
|
if !config.Exists() {
|
||||||
fmt.Println("First time setup detected!")
|
// No config yet. If we have a real terminal, run the rich TUI setup
|
||||||
cfg, err := profiler.RunFirstTimeSetup()
|
// (huh forms). Otherwise — typically when the user double-clicked the
|
||||||
if err != nil {
|
// shortcut on Windows — write defaults silently and let the React
|
||||||
fmt.Fprintf(os.Stderr, "Setup error: %v\n", err)
|
// onboarding wizard handle the real first-run flow once the browser
|
||||||
os.Exit(1)
|
// opens. This avoids huh aborting with "This is a command line tool".
|
||||||
}
|
if isInteractiveStdin() {
|
||||||
|
fmt.Println("First time setup detected!")
|
||||||
|
cfg, err := profiler.RunFirstTimeSetup()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Setup error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
for i := range cfg.AI.Providers {
|
for i := range cfg.AI.Providers {
|
||||||
if cfg.AI.Providers[i].Active && cfg.AI.Providers[i].APIKey == "" {
|
if cfg.AI.Providers[i].Active && cfg.AI.Providers[i].APIKey == "" {
|
||||||
key, err := profiler.AskAPIKey(cfg.AI.Providers[i].Name)
|
key, err := profiler.AskAPIKey(cfg.AI.Providers[i].Name)
|
||||||
if err == nil && key != "" {
|
if err == nil && key != "" {
|
||||||
cfg.AI.Providers[i].APIKey = key
|
cfg.AI.Providers[i].APIKey = key
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := config.Save(cfg); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Save error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("\nSetup complete! Starting muyue...")
|
||||||
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Non-interactive — skip the TUI, persist defaults, web onboarding
|
||||||
|
// will fill in the profile / API keys.
|
||||||
|
cfg := config.Default()
|
||||||
if err := config.Save(cfg); err != nil {
|
if err := config.Save(cfg); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Save error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Save error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("\nSetup complete! Starting muyue...")
|
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
54
cmd/muyue/console_windows.go
Normal file
54
cmd/muyue/console_windows.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Windows-only: with -H=windowsgui the binary is registered as a GUI
|
||||||
|
// subsystem app, so double-clicking from the Desktop shortcut does NOT
|
||||||
|
// spawn a console window (good for the desktop UX). The downside is that
|
||||||
|
// sub-commands like `muyue scan`, `muyue version`, `muyue install-shortcuts`
|
||||||
|
// produce no output when invoked from cmd.exe.
|
||||||
|
//
|
||||||
|
// Workaround: at process start, try to attach to the parent's console via
|
||||||
|
// kernel32!AttachConsole(ATTACH_PARENT_PROCESS). If the parent has a console
|
||||||
|
// (i.e. we were launched from cmd.exe / PowerShell), stdout/stderr/stdin are
|
||||||
|
// rebound to it. If not (Explorer double-click), the call fails silently and
|
||||||
|
// the binary runs without any console — exactly what we want.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const attachParentProcess = ^uint32(0) // -1 cast to DWORD
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
kernel32, err := syscall.LoadLibrary("kernel32.dll")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer syscall.FreeLibrary(kernel32)
|
||||||
|
attachConsole, err := syscall.GetProcAddress(kernel32, "AttachConsole")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r0, _, _ := syscall.SyscallN(attachConsole, uintptr(attachParentProcess))
|
||||||
|
if r0 == 0 {
|
||||||
|
return // parent has no console (Explorer launch) — stay silent
|
||||||
|
}
|
||||||
|
// Re-bind the standard streams to the freshly attached console so
|
||||||
|
// fmt.Println / log output appear in the parent terminal.
|
||||||
|
if h, err := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE); err == nil && h != 0 {
|
||||||
|
os.Stdout = os.NewFile(uintptr(h), "stdout")
|
||||||
|
}
|
||||||
|
if h, err := syscall.GetStdHandle(syscall.STD_ERROR_HANDLE); err == nil && h != 0 {
|
||||||
|
os.Stderr = os.NewFile(uintptr(h), "stderr")
|
||||||
|
}
|
||||||
|
if h, err := syscall.GetStdHandle(syscall.STD_INPUT_HANDLE); err == nil && h != 0 {
|
||||||
|
os.Stdin = os.NewFile(uintptr(h), "stdin")
|
||||||
|
}
|
||||||
|
// log.Default() captured the original os.Stderr at init time — repoint it
|
||||||
|
// at the freshly attached console so log.Printf calls (e.g. desktop.Run)
|
||||||
|
// surface in the parent terminal.
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
Name = "muyue"
|
Name = "muyue"
|
||||||
Version = "0.7.4"
|
Version = "0.7.5"
|
||||||
Author = "La Légion de Muyue"
|
Author = "La Légion de Muyue"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user