mirror of
https://github.com/safing/portmaster
synced 2025-09-01 10:09:11 +00:00
Fix console/window handling on windows
This commit is contained in:
parent
1cc90f1eee
commit
b30bb25ce3
5 changed files with 223 additions and 25 deletions
24
pmctl/build
24
pmctl/build
|
@ -43,10 +43,32 @@ if [[ "$BUILD_SOURCE" == "" ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# build tools
|
||||
EXTRA_LD_FLAGS=""
|
||||
if [[ $GOOS == "windows" ]]; then
|
||||
# checks
|
||||
if [[ $CC_FOR_windows_amd64 == "" ]]; then
|
||||
echo "ENV variable CC_FOR_windows_amd64 (c compiler) is not set. Please set it to the cross compiler you want to use for compiling for windows_amd64"
|
||||
exit 1
|
||||
fi
|
||||
if [[ $CXX_FOR_windows_amd64 == "" ]]; then
|
||||
echo "ENV variable CXX_FOR_windows_amd64 (c++ compiler) is not set. Please set it to the cross compiler you want to use for compiling for windows_amd64"
|
||||
exit 1
|
||||
fi
|
||||
# compilers
|
||||
export CC=$CC_FOR_windows_amd64
|
||||
export CXX=$CXX_FOR_windows_amd64
|
||||
# custom
|
||||
export CGO_ENABLED=1
|
||||
EXTRA_LD_FLAGS='-H windowsgui' # Hide console window by default (but we attach to parent console if available)
|
||||
# generate resource.syso for windows metadata / icon
|
||||
go generate
|
||||
fi
|
||||
|
||||
echo "Please notice, that this build script includes metadata into the build."
|
||||
echo "This information is useful for debugging and license compliance."
|
||||
echo "Run the compiled binary with the -version flag to see the information included."
|
||||
|
||||
# build
|
||||
BUILD_PATH="github.com/safing/portbase/info"
|
||||
go build -ldflags "-X ${BUILD_PATH}.commit=${BUILD_COMMIT} -X ${BUILD_PATH}.buildOptions=${BUILD_BUILDOPTIONS} -X ${BUILD_PATH}.buildUser=${BUILD_USER} -X ${BUILD_PATH}.buildHost=${BUILD_HOST} -X ${BUILD_PATH}.buildDate=${BUILD_DATE} -X ${BUILD_PATH}.buildSource=${BUILD_SOURCE}" $*
|
||||
go build -ldflags "$EXTRA_LD_FLAGS -X ${BUILD_PATH}.commit=${BUILD_COMMIT} -X ${BUILD_PATH}.buildOptions=${BUILD_BUILDOPTIONS} -X ${BUILD_PATH}.buildUser=${BUILD_USER} -X ${BUILD_PATH}.buildHost=${BUILD_HOST} -X ${BUILD_PATH}.buildDate=${BUILD_DATE} -X ${BUILD_PATH}.buildSource=${BUILD_SOURCE}" $*
|
||||
|
|
10
pmctl/console_linux.go
Normal file
10
pmctl/console_linux.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func attachToParentConsole() (attached bool, err error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func hideWindow(cmd *exec.Cmd) {
|
||||
}
|
150
pmctl/console_windows.go
Normal file
150
pmctl/console_windows.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
package main
|
||||
|
||||
// Parts of this file are FORKED
|
||||
// from https://github.com/apenwarr/fixconsole/blob/35b2e7d921eb80a71a5f04f166ff0a1405bddf79/fixconsole_windows.go
|
||||
// on 16.07.2019
|
||||
// with Apache-2.0 license
|
||||
// authored by https://github.com/apenwarr
|
||||
|
||||
// docs/sources:
|
||||
// Stackoverflow Question: https://stackoverflow.com/questions/23743217/printing-output-to-a-command-window-when-golang-application-is-compiled-with-ld
|
||||
// MS AttachConsole: https://docs.microsoft.com/en-us/windows/console/attachconsole
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
windowsAttachParentProcess = ^uintptr(0) // (DWORD)-1
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procAttachConsole = kernel32.NewProc("AttachConsole")
|
||||
)
|
||||
|
||||
// Windows console output is a mess.
|
||||
//
|
||||
// If you compile as "-H windows", then if you launch your program without
|
||||
// a console, Windows forcibly creates one to use as your stdin/stdout, which
|
||||
// is silly for a GUI app, so we can't do that.
|
||||
//
|
||||
// If you compile as "-H windowsgui", then it doesn't create a console for
|
||||
// your app... but also doesn't provide a working stdin/stdout/stderr even if
|
||||
// you *did* launch from the console. However, you can use AttachConsole()
|
||||
// to get a handle to your parent process's console, if any, and then
|
||||
// os.NewFile() to turn that handle into a fd usable as stdout/stderr.
|
||||
//
|
||||
// However, then you have the problem that if you redirect stdout or stderr
|
||||
// from the shell, you end up ignoring the redirection by forcing it to the
|
||||
// console.
|
||||
//
|
||||
// To fix *that*, we have to detect whether there was a pre-existing stdout
|
||||
// or not. We can check GetStdHandle(), which returns 0 for "should be
|
||||
// console" and nonzero for "already pointing at a file."
|
||||
//
|
||||
// Be careful though! As soon as you run AttachConsole(), it resets *all*
|
||||
// the GetStdHandle() handles to point them at the console instead, thus
|
||||
// throwing away the original file redirects. So we have to GetStdHandle()
|
||||
// *before* AttachConsole().
|
||||
//
|
||||
// For some reason, powershell redirections provide a valid file handle, but
|
||||
// writing to that handle doesn't write to the file. I haven't found a way
|
||||
// to work around that. (Windows 10.0.17763.379)
|
||||
//
|
||||
// Net result is as follows.
|
||||
// Before:
|
||||
// SHELL NON-REDIRECTED REDIRECTED
|
||||
// explorer.exe no console n/a
|
||||
// cmd.exe broken works
|
||||
// powershell broken broken
|
||||
// WSL bash broken works
|
||||
// After
|
||||
// SHELL NON-REDIRECTED REDIRECTED
|
||||
// explorer.exe no console n/a
|
||||
// cmd.exe works works
|
||||
// powershell works broken
|
||||
// WSL bash works works
|
||||
//
|
||||
// We don't seem to make anything worse, at least.
|
||||
func attachToParentConsole() (attached bool, err error) {
|
||||
// get std handles before we attempt to attach to parent console
|
||||
stdin, _ := syscall.GetStdHandle(syscall.STD_INPUT_HANDLE)
|
||||
stdout, _ := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
|
||||
stderr, _ := syscall.GetStdHandle(syscall.STD_ERROR_HANDLE)
|
||||
|
||||
// attempt to attach to parent console
|
||||
err = procAttachConsole.Find()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
r1, _, err := procAttachConsole.Call(windowsAttachParentProcess)
|
||||
if r1 == 0 {
|
||||
// possible errors:
|
||||
// ERROR_ACCESS_DENIED: already attached to console
|
||||
// ERROR_INVALID_HANDLE: process does not have console
|
||||
// ERROR_INVALID_PARAMETER: process does not exist
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// get std handles after we attached to console
|
||||
var invalid syscall.Handle
|
||||
con := invalid
|
||||
|
||||
if stdin == invalid {
|
||||
stdin, _ = syscall.GetStdHandle(syscall.STD_INPUT_HANDLE)
|
||||
}
|
||||
if stdout == invalid {
|
||||
stdout, _ = syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE)
|
||||
con = stdout
|
||||
}
|
||||
if stderr == invalid {
|
||||
stderr, _ = syscall.GetStdHandle(syscall.STD_ERROR_HANDLE)
|
||||
con = stderr
|
||||
}
|
||||
|
||||
// correct output mode
|
||||
if con != invalid {
|
||||
// Make sure the console is configured to convert
|
||||
// \n to \r\n, like Go programs expect.
|
||||
h := windows.Handle(con)
|
||||
var st uint32
|
||||
err := windows.GetConsoleMode(h, &st)
|
||||
if err != nil {
|
||||
fmt.Printf("%s failed to get console mode: %s\n", logPrefix, err)
|
||||
} else {
|
||||
err = windows.SetConsoleMode(h, st&^windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||
if err != nil {
|
||||
fmt.Printf("%s failed to set console mode: %s\n", logPrefix, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fix std handles to correct values (ie. redirects)
|
||||
if stdin != invalid {
|
||||
os.Stdin = os.NewFile(uintptr(stdin), "stdin")
|
||||
fmt.Printf("%s fixed os.Stdin after attaching to parent console\n", logPrefix)
|
||||
}
|
||||
if stdout != invalid {
|
||||
os.Stdout = os.NewFile(uintptr(stdout), "stdout")
|
||||
fmt.Printf("%s fixed os.Stdout after attaching to parent console\n", logPrefix)
|
||||
}
|
||||
if stderr != invalid {
|
||||
os.Stderr = os.NewFile(uintptr(stderr), "stderr")
|
||||
fmt.Printf("%s fixed os.Stderr after attaching to parent console\n", logPrefix)
|
||||
}
|
||||
|
||||
fmt.Printf("%s attached to parent console\n", logPrefix)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func hideWindow(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
}
|
||||
}
|
|
@ -2,13 +2,9 @@ package main
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portbase/info"
|
||||
"github.com/safing/portbase/log"
|
||||
|
@ -35,6 +31,9 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
// Let cobra ignore if we are running as "GUI" or not
|
||||
cobra.MousetrapHelpText = ""
|
||||
|
||||
databaseRootDir = rootCmd.PersistentFlags().String("db", "", "set database directory")
|
||||
err := rootCmd.MarkPersistentFlagRequired("db")
|
||||
if err != nil {
|
||||
|
@ -43,7 +42,14 @@ func init() {
|
|||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
var err error
|
||||
|
||||
// check if we are running in a console (try to attach to parent console if available)
|
||||
runningInConsole, err = attachToParentConsole()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to attach to parent console: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// not using portbase logger
|
||||
log.SetLogLevel(log.CriticalLevel)
|
||||
|
@ -58,10 +64,10 @@ func main() {
|
|||
// }()
|
||||
|
||||
// set meta info
|
||||
info.Set("Portmaster Control", "0.2.1", "AGPLv3", true)
|
||||
info.Set("Portmaster Control", "0.2.5", "AGPLv3", true)
|
||||
|
||||
// check if meta info is ok
|
||||
err := info.CheckVersion()
|
||||
err = info.CheckVersion()
|
||||
if err != nil {
|
||||
fmt.Printf("%s compile error: please compile using the provided build script\n", logPrefix)
|
||||
os.Exit(1)
|
||||
|
@ -73,14 +79,13 @@ func main() {
|
|||
}
|
||||
|
||||
// start root command
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
if err = rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func initPmCtl(cmd *cobra.Command, args []string) error {
|
||||
|
||||
func initPmCtl(cmd *cobra.Command, args []string) (err error) {
|
||||
// transform from db base path to updates path
|
||||
if *databaseRootDir != "" {
|
||||
updates.SetDatabaseRoot(*databaseRootDir)
|
||||
|
|
39
pmctl/run.go
39
pmctl/run.go
|
@ -13,10 +13,16 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
runningInConsole bool
|
||||
onWindows = runtime.GOOS == "windows"
|
||||
)
|
||||
|
||||
// Options for starting component
|
||||
type Options struct {
|
||||
Identifier string
|
||||
AllowDownload bool
|
||||
Identifier string
|
||||
AllowDownload bool
|
||||
AllowHidingWindow bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -36,8 +42,9 @@ var runCore = &cobra.Command{
|
|||
Short: "Run the Portmaster Core",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return run(cmd, &Options{
|
||||
Identifier: "core/portmaster-core",
|
||||
AllowDownload: true,
|
||||
Identifier: "core/portmaster-core",
|
||||
AllowDownload: true,
|
||||
AllowHidingWindow: true,
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
|
@ -51,8 +58,9 @@ var runApp = &cobra.Command{
|
|||
Short: "Run the Portmaster App",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return run(cmd, &Options{
|
||||
Identifier: "app/portmaster-app",
|
||||
AllowDownload: false,
|
||||
Identifier: "app/portmaster-app",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: false,
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
|
@ -66,8 +74,9 @@ var runNotifier = &cobra.Command{
|
|||
Short: "Run the Portmaster Notifier",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return run(cmd, &Options{
|
||||
Identifier: "notifier/portmaster-notifier",
|
||||
AllowDownload: false,
|
||||
Identifier: "notifier/portmaster-notifier",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: true,
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
|
@ -86,7 +95,7 @@ func run(cmd *cobra.Command, opts *Options) error {
|
|||
args = os.Args[3:]
|
||||
|
||||
// adapt identifier
|
||||
if windows() {
|
||||
if onWindows {
|
||||
opts.Identifier += ".exe"
|
||||
}
|
||||
|
||||
|
@ -98,7 +107,7 @@ func run(cmd *cobra.Command, opts *Options) error {
|
|||
}
|
||||
|
||||
// check permission
|
||||
if !windows() {
|
||||
if !onWindows {
|
||||
info, err := os.Stat(file.Path())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get file info on %s: %s", file.Path(), err)
|
||||
|
@ -116,6 +125,12 @@ func run(cmd *cobra.Command, opts *Options) error {
|
|||
// create command
|
||||
exc := exec.Command(file.Path(), args...)
|
||||
|
||||
if !runningInConsole && opts.AllowHidingWindow {
|
||||
// Windows only:
|
||||
// only hide (all) windows of program if we are not running in console and windows may be hidden
|
||||
hideWindow(exc)
|
||||
}
|
||||
|
||||
// consume stdout/stderr
|
||||
stdout, err := exc.StdoutPipe()
|
||||
if err != nil {
|
||||
|
@ -192,7 +207,3 @@ func run(cmd *cobra.Command, opts *Options) error {
|
|||
fmt.Printf("%s %s completed successfully\n", logPrefix, opts.Identifier)
|
||||
return nil
|
||||
}
|
||||
|
||||
func windows() bool {
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue