package console import ( "fmt" "os" "github.com/pkg/errors" "golang.org/x/sys/windows" ) var ( vtInputSupported bool ErrNotImplemented = errors.New("not implemented") ) func (m *master) initStdios() { m.in = windows.Handle(os.Stdin.Fd()) if err := windows.GetConsoleMode(m.in, &m.inMode); err == nil { // Validate that windows.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it. if err = windows.SetConsoleMode(m.in, m.inMode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err == nil { vtInputSupported = true } // Unconditionally set the console mode back even on failure because SetConsoleMode // remembers invalid bits on input handles. windows.SetConsoleMode(m.in, m.inMode) } else { fmt.Printf("failed to get console mode for stdin: %v\n", err) } m.out = windows.Handle(os.Stdout.Fd()) if err := windows.GetConsoleMode(m.out, &m.outMode); err == nil { if err := windows.SetConsoleMode(m.out, m.outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil { m.outMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING } else { windows.SetConsoleMode(m.out, m.outMode) } } else { fmt.Printf("failed to get console mode for stdout: %v\n", err) } m.err = windows.Handle(os.Stderr.Fd()) if err := windows.GetConsoleMode(m.err, &m.errMode); err == nil { if err := windows.SetConsoleMode(m.err, m.errMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil { m.errMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING } else { windows.SetConsoleMode(m.err, m.errMode) } } else { fmt.Printf("failed to get console mode for stderr: %v\n", err) } } type master struct { in windows.Handle inMode uint32 out windows.Handle outMode uint32 err windows.Handle errMode uint32 } func (m *master) SetRaw() error { if err := makeInputRaw(m.in, m.inMode); err != nil { return err } // Set StdOut and StdErr to raw mode, we ignore failures since // windows.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this version of // Windows. windows.SetConsoleMode(m.out, m.outMode|windows.DISABLE_NEWLINE_AUTO_RETURN) windows.SetConsoleMode(m.err, m.errMode|windows.DISABLE_NEWLINE_AUTO_RETURN) return nil } func (m *master) Reset() error { for _, s := range []struct { fd windows.Handle mode uint32 }{ {m.in, m.inMode}, {m.out, m.outMode}, {m.err, m.errMode}, } { if err := windows.SetConsoleMode(s.fd, s.mode); err != nil { return errors.Wrap(err, "unable to restore console mode") } } return nil } func (m *master) Size() (WinSize, error) { var info windows.ConsoleScreenBufferInfo err := windows.GetConsoleScreenBufferInfo(m.out, &info) if err != nil { return WinSize{}, errors.Wrap(err, "unable to get console info") } winsize := WinSize{ Width: uint16(info.Window.Right - info.Window.Left + 1), Height: uint16(info.Window.Bottom - info.Window.Top + 1), } return winsize, nil } func (m *master) Resize(ws WinSize) error { return ErrNotImplemented } func (m *master) ResizeFrom(c Console) error { return ErrNotImplemented } func (m *master) DisableEcho() error { mode := m.inMode &^ windows.ENABLE_ECHO_INPUT mode |= windows.ENABLE_PROCESSED_INPUT mode |= windows.ENABLE_LINE_INPUT if err := windows.SetConsoleMode(m.in, mode); err != nil { return errors.Wrap(err, "unable to set console to disable echo") } return nil } func (m *master) Close() error { return nil } func (m *master) Read(b []byte) (int, error) { panic("not implemented on windows") } func (m *master) Write(b []byte) (int, error) { panic("not implemented on windows") } func (m *master) Fd() uintptr { return uintptr(m.in) } // on windows, console can only be made from os.Std{in,out,err}, hence there // isnt a single name here we can use. Return a dummy "console" value in this // case should be sufficient. func (m *master) Name() string { return "console" } // makeInputRaw puts the terminal (Windows Console) connected to the given // file descriptor into raw mode func makeInputRaw(fd windows.Handle, mode uint32) error { // See // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx // Disable these modes mode &^= windows.ENABLE_ECHO_INPUT mode &^= windows.ENABLE_LINE_INPUT mode &^= windows.ENABLE_MOUSE_INPUT mode &^= windows.ENABLE_WINDOW_INPUT mode &^= windows.ENABLE_PROCESSED_INPUT // Enable these modes mode |= windows.ENABLE_EXTENDED_FLAGS mode |= windows.ENABLE_INSERT_MODE mode |= windows.ENABLE_QUICK_EDIT_MODE if vtInputSupported { mode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT } if err := windows.SetConsoleMode(fd, mode); err != nil { return errors.Wrap(err, "unable to set console to raw mode") } return nil } func checkConsole(f *os.File) error { var mode uint32 if err := windows.GetConsoleMode(windows.Handle(f.Fd()), &mode); err != nil { return err } return nil } func newMaster(f *os.File) (Console, error) { if f != os.Stdin && f != os.Stdout && f != os.Stderr { return nil, errors.New("creating a console from a file is not supported on windows") } m := &master{} m.initStdios() return m, nil }