| //go:build windows |
| // +build windows |
| |
| package terminal |
| |
| import ( |
| "fmt" |
| "os" |
| "syscall" |
| |
| "golang.org/x/sys/windows" |
| |
| // We're continuing to use this third-party library on Windows because it |
| // has the additional IsCygwinTerminal function, which includes some useful |
| // heuristics for recognizing when a pipe seems to be connected to a |
| // legacy terminal emulator on Windows versions that lack true pty support. |
| // We now use golang.org/x/term's functionality on other platforms. |
| isatty "github.com/mattn/go-isatty" |
| ) |
| |
| func configureOutputHandle(f *os.File) (*OutputStream, error) { |
| ret := &OutputStream{ |
| File: f, |
| } |
| |
| if fd := f.Fd(); isatty.IsTerminal(fd) { |
| // We have a few things to deal with here: |
| // - Activating UTF-8 output support (mandatory) |
| // - Activating virtual terminal support (optional) |
| // These will not succeed on Windows 8 or early versions of Windows 10. |
| |
| // UTF-8 support means switching the console "code page" to CP_UTF8. |
| // Notice that this doesn't take the specific file descriptor, because |
| // the console is just ambiently associated with our process. |
| err := SetConsoleOutputCP(CP_UTF8) |
| if err != nil { |
| return nil, fmt.Errorf("failed to set the console to UTF-8 mode; you may need to use a newer version of Windows: %s", err) |
| } |
| |
| // If the console also allows us to turn on |
| // ENABLE_VIRTUAL_TERMINAL_PROCESSING then we can potentially use VT |
| // output, although the methods of Settings will make the final |
| // determination on that because we might have some handles pointing at |
| // terminals and other handles pointing at files/pipes. |
| ret.getColumns = getColumnsWindowsConsole |
| var mode uint32 |
| err = windows.GetConsoleMode(windows.Handle(fd), &mode) |
| if err != nil { |
| return ret, nil // We'll treat this as success but without VT support |
| } |
| mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING |
| err = windows.SetConsoleMode(windows.Handle(fd), mode) |
| if err != nil { |
| return ret, nil // We'll treat this as success but without VT support |
| } |
| |
| // If we get here then we've successfully turned on VT processing, so |
| // we can return an OutputStream that answers true when asked if it |
| // is a Terminal. |
| ret.isTerminal = staticTrue |
| return ret, nil |
| |
| } else if isatty.IsCygwinTerminal(fd) { |
| // Cygwin terminals -- and other VT100 "fakers" for older versions of |
| // Windows -- are not really terminals in the usual sense, but rather |
| // are pipes between the child process (Terraform) and the terminal |
| // emulator. isatty.IsCygwinTerminal uses some heuristics to |
| // distinguish those pipes from other pipes we might see if the user |
| // were, for example, using the | operator on the command line. |
| // If we get in here then we'll assume that we can send VT100 sequences |
| // to this stream, even though it isn't a terminal in the usual sense. |
| |
| ret.isTerminal = staticTrue |
| // TODO: Is it possible to detect the width of these fake terminals? |
| return ret, nil |
| } |
| |
| // If we fall out here then we have a non-terminal filehandle, so we'll |
| // just accept all of the default OutputStream behaviors |
| return ret, nil |
| } |
| |
| func configureInputHandle(f *os.File) (*InputStream, error) { |
| ret := &InputStream{ |
| File: f, |
| } |
| |
| if fd := f.Fd(); isatty.IsTerminal(fd) { |
| // We have to activate UTF-8 input, or else we fail. This will not |
| // succeed on Windows 8 or early versions of Windows 10. |
| // Notice that this doesn't take the specific file descriptor, because |
| // the console is just ambiently associated with our process. |
| err := SetConsoleCP(CP_UTF8) |
| if err != nil { |
| return nil, fmt.Errorf("failed to set the console to UTF-8 mode; you may need to use a newer version of Windows: %s", err) |
| } |
| ret.isTerminal = staticTrue |
| return ret, nil |
| } else if isatty.IsCygwinTerminal(fd) { |
| // As with the output handles above, we'll use isatty's heuristic to |
| // pretend that a pipe from mintty or a similar userspace terminal |
| // emulator is actually a terminal. |
| ret.isTerminal = staticTrue |
| return ret, nil |
| } |
| |
| // If we fall out here then we have a non-terminal filehandle, so we'll |
| // just accept all of the default InputStream behaviors |
| return ret, nil |
| } |
| |
| func getColumnsWindowsConsole(f *os.File) int { |
| // We'll just unconditionally ask the given file for its console buffer |
| // info here, and let it fail if the file isn't actually a console. |
| // (In practice, the init functions above only hook up this function |
| // if the handle looks like a console, so this should succeed.) |
| var info windows.ConsoleScreenBufferInfo |
| err := windows.GetConsoleScreenBufferInfo(windows.Handle(f.Fd()), &info) |
| if err != nil { |
| return defaultColumns |
| } |
| return int(info.Size.X) |
| } |
| |
| // Unfortunately not all of the Windows kernel functions we need are in |
| // x/sys/windows at the time of writing, so we need to call some of them |
| // directly. (If you're maintaining this in future and have the capacity to |
| // test it well, consider checking if these functions have been added upstream |
| // yet and switch to their wrapper stubs if so. |
| var modkernel32 = windows.NewLazySystemDLL("kernel32.dll") |
| var procSetConsoleCP = modkernel32.NewProc("SetConsoleCP") |
| var procSetConsoleOutputCP = modkernel32.NewProc("SetConsoleOutputCP") |
| |
| const CP_UTF8 = 65001 |
| |
| // (These are written in the style of the stubs in x/sys/windows, which is |
| // a little non-idiomatic just due to the awkwardness of the low-level syscall |
| // interface.) |
| |
| func SetConsoleCP(codepageID uint32) (err error) { |
| r1, _, e1 := syscall.Syscall(procSetConsoleCP.Addr(), 1, uintptr(codepageID), 0, 0) |
| if r1 == 0 { |
| err = e1 |
| } |
| return |
| } |
| |
| func SetConsoleOutputCP(codepageID uint32) (err error) { |
| r1, _, e1 := syscall.Syscall(procSetConsoleOutputCP.Addr(), 1, uintptr(codepageID), 0, 0) |
| if r1 == 0 { |
| err = e1 |
| } |
| return |
| } |
| |
| func staticTrue(f *os.File) bool { |
| return true |
| } |
| |
| func staticFalse(f *os.File) bool { |
| return false |
| } |