diff --git a/doc/thirdparty.txt b/doc/thirdparty.txt index 65e3e73..9a7848d 100644 --- a/doc/thirdparty.txt +++ b/doc/thirdparty.txt @@ -20,3 +20,8 @@ The licenses are included, and the authorship comments are left intact. https://github.com/getlantern/systray (commit:2c0986d) Apache 2.0 removed golog dependency + +- fixconsole + https://github.com/apenwarr/fixconsole (commit:5a9f648) Apache 2.0 + + removed `w32` dependency diff --git a/doc/todo.txt b/doc/todo.txt index fb20fb8..03a209e 100644 --- a/doc/todo.txt +++ b/doc/todo.txt @@ -1,6 +1,3 @@ - feedlist keyboard navigation is flaky in "unread" section -- windows cli mode not working - incorporate changes from: - https://github.com/apenwarr/fixconsole/blob/master/fixconsole_windows.go - make feed settings available in all filter modes - show `mark as read` in `all feeds` filter (along with numbers) diff --git a/src/platform/fixconsole_default.go b/src/platform/fixconsole_default.go new file mode 100644 index 0000000..1f826c5 --- /dev/null +++ b/src/platform/fixconsole_default.go @@ -0,0 +1,14 @@ +// +build !windows + +package platform + +// On non-windows platforms, we don't need to do anything. The console +// starts off attached already, if it exists. + +func AttachConsole() error { + return nil +} + +func FixConsoleIfNeeded() error { + return nil +} diff --git a/src/platform/fixconsole_windows.go b/src/platform/fixconsole_windows.go new file mode 100644 index 0000000..03c5641 --- /dev/null +++ b/src/platform/fixconsole_windows.go @@ -0,0 +1,131 @@ +package platform + +import ( + "fmt" + "golang.org/x/sys/windows" + "os" + "syscall" +) + +func AttachConsole() error { + const ATTACH_PARENT_PROCESS = ^uintptr(0) + proc := syscall.MustLoadDLL("kernel32.dll").MustFindProc("AttachConsole") + r1, _, err := proc.Call(ATTACH_PARENT_PROCESS) + if r1 == 0 { + errno, ok := err.(syscall.Errno) + if ok && errno == windows.ERROR_INVALID_HANDLE { + // console handle doesn't exist; not a real + // error, but the console handle will be + // invalid. + return nil + } + return err + } else { + return nil + } +} + +var oldStdin, oldStdout, oldStderr *os.File + +// 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 FixConsoleIfNeeded() error { + // Retain the original console objects, to prevent Go from automatically + // closing their file descriptors when they get garbage collected. + // You never want to close file descriptors 0, 1, and 2. + oldStdin, oldStdout, oldStderr = os.Stdin, os.Stdout, os.Stderr + + stdin, _ := syscall.GetStdHandle(syscall.STD_INPUT_HANDLE) + stdout, _ := syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE) + stderr, _ := syscall.GetStdHandle(syscall.STD_ERROR_HANDLE) + + var invalid syscall.Handle + con := invalid + + if stdin == invalid || stdout == invalid || stderr == invalid { + err := AttachConsole() + if err != nil { + return fmt.Errorf("attachconsole: %v", err) + } + + 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 + } + } + + 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 { + return fmt.Errorf("GetConsoleMode: %v", err) + } + err = windows.SetConsoleMode(h, st&^windows.DISABLE_NEWLINE_AUTO_RETURN) + if err != nil { + return fmt.Errorf("SetConsoleMode: %v", err) + } + } + + if stdin != invalid { + os.Stdin = os.NewFile(uintptr(stdin), "stdin") + } + if stdout != invalid { + os.Stdout = os.NewFile(uintptr(stdout), "stdout") + } + if stderr != invalid { + os.Stderr = os.NewFile(uintptr(stderr), "stderr") + } + return nil +}