diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index e72a8e5..e1b6ccf 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -92,7 +92,11 @@ jobs: SHORT_SHA="${GITHUB_SHA:0:7}" BUILD_DATE="$(date -u +%Y-%m-%d)" mkdir -p bin - go build -trimpath -ldflags="-s -w \ + # -H=windowsgui = subsystem WINDOWS, double-click no longer + # flashes a console window. main.go calls AttachConsole on + # startup so CLI invocations from cmd/PowerShell still print + # to the parent terminal. + go build -trimpath -ldflags="-s -w -H=windowsgui \ -X main.Version=dev-${SHORT_SHA} \ -X main.Commit=${SHORT_SHA} \ -X main.BuildDate=${BUILD_DATE}" \ diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index a6c9cf0..4cd2b17 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -67,7 +67,11 @@ jobs: BUILD_DATE="$(date -u +%Y-%m-%d)" SHORT_SHA="${GITHUB_SHA:0:7}" mkdir -p dist - go build -trimpath -ldflags="-s -w \ + # -H=windowsgui makes drover.exe a GUI subsystem binary so the + # double-click experience doesn't flash a console window. main.go + # calls AttachConsole on startup so CLI runs still print to the + # parent terminal when launched from cmd/PowerShell. + go build -trimpath -ldflags="-s -w -H=windowsgui \ -X main.Version=${{ steps.version.outputs.version }} \ -X main.Commit=${SHORT_SHA} \ -X main.BuildDate=${BUILD_DATE}" \ diff --git a/cmd/drover/console_other.go b/cmd/drover/console_other.go new file mode 100644 index 0000000..3226372 --- /dev/null +++ b/cmd/drover/console_other.go @@ -0,0 +1,5 @@ +//go:build !windows + +package main + +func attachToParentConsole() {} diff --git a/cmd/drover/console_windows.go b/cmd/drover/console_windows.go new file mode 100644 index 0000000..90656ee --- /dev/null +++ b/cmd/drover/console_windows.go @@ -0,0 +1,46 @@ +//go:build windows + +package main + +import ( + "os" + "syscall" + + "golang.org/x/sys/windows" +) + +// attachToParentConsole reconnects stdin/stdout/stderr to the console of the +// process that launched us, if any. It is a no-op when the binary was started +// from Explorer (double-click) — there is no parent console to attach to. +// +// Why we need this: the binary is built with -H=windowsgui, which means +// Windows treats it as a GUI application and does NOT allocate a console +// when it starts. That gives a clean double-click experience (no flashing +// console window). But it also disconnects fmt.Println output when the +// user runs us from cmd.exe / PowerShell. Calling AttachConsole here +// re-attaches us to the parent's console so CLI output works as expected. +func attachToParentConsole() { + const ATTACH_PARENT_PROCESS = ^uint32(0) // (DWORD)-1 + + kernel32 := windows.NewLazySystemDLL("kernel32.dll") + attachConsole := kernel32.NewProc("AttachConsole") + + r, _, _ := attachConsole.Call(uintptr(ATTACH_PARENT_PROCESS)) + if r == 0 { + // AttachConsole failed — most likely there is no parent console + // (we were started from Explorer). That's fine: GUI mode is the + // default UX and stdout/stderr just go nowhere. + return + } + + // Re-bind os.Stdout / os.Stderr to the freshly-attached console. + 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") + } +} diff --git a/cmd/drover/gui_windows.go b/cmd/drover/gui_windows.go index c8b12d3..38c77e9 100644 --- a/cmd/drover/gui_windows.go +++ b/cmd/drover/gui_windows.go @@ -38,8 +38,10 @@ func showTestWindow() { bodyW, _ := windows.UTF16PtrFromString(body) titleW, _ := windows.UTF16PtrFromString(title) - // MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND - const flags = 0x00000000 | 0x00000040 | 0x00010000 + // MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND | MB_TOPMOST + // MB_TOPMOST is essential — without it the message box can pop up + // behind other windows and the user thinks nothing happened. + const flags = 0x00000000 | 0x00000040 | 0x00010000 | 0x00040000 messageBox.Call( 0, diff --git a/cmd/drover/main.go b/cmd/drover/main.go index 748582f..7c246d0 100644 --- a/cmd/drover/main.go +++ b/cmd/drover/main.go @@ -22,6 +22,12 @@ var ( var configPath string func main() { + // On Windows the binary is linked with -H=windowsgui so a double-click + // doesn't flash a console window. When the user runs us from cmd or + // PowerShell we still want stdout/stderr to land in their terminal — + // AttachConsole(ATTACH_PARENT_PROCESS) wires that up. No-op elsewhere. + attachToParentConsole() + // Inject our build version so the updater package can stamp it on the // User-Agent header it sends to git.okcu.io. updater.SetVersion(Version)