1/* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15*/ 16 17package console 18 19import ( 20 "fmt" 21 "os" 22 23 "github.com/pkg/errors" 24 "golang.org/x/sys/windows" 25) 26 27var ( 28 vtInputSupported bool 29 ErrNotImplemented = errors.New("not implemented") 30) 31 32func (m *master) initStdios() { 33 m.in = windows.Handle(os.Stdin.Fd()) 34 if err := windows.GetConsoleMode(m.in, &m.inMode); err == nil { 35 // Validate that windows.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it. 36 if err = windows.SetConsoleMode(m.in, m.inMode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err == nil { 37 vtInputSupported = true 38 } 39 // Unconditionally set the console mode back even on failure because SetConsoleMode 40 // remembers invalid bits on input handles. 41 windows.SetConsoleMode(m.in, m.inMode) 42 } else { 43 fmt.Printf("failed to get console mode for stdin: %v\n", err) 44 } 45 46 m.out = windows.Handle(os.Stdout.Fd()) 47 if err := windows.GetConsoleMode(m.out, &m.outMode); err == nil { 48 if err := windows.SetConsoleMode(m.out, m.outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil { 49 m.outMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING 50 } else { 51 windows.SetConsoleMode(m.out, m.outMode) 52 } 53 } else { 54 fmt.Printf("failed to get console mode for stdout: %v\n", err) 55 } 56 57 m.err = windows.Handle(os.Stderr.Fd()) 58 if err := windows.GetConsoleMode(m.err, &m.errMode); err == nil { 59 if err := windows.SetConsoleMode(m.err, m.errMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil { 60 m.errMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING 61 } else { 62 windows.SetConsoleMode(m.err, m.errMode) 63 } 64 } else { 65 fmt.Printf("failed to get console mode for stderr: %v\n", err) 66 } 67} 68 69type master struct { 70 in windows.Handle 71 inMode uint32 72 73 out windows.Handle 74 outMode uint32 75 76 err windows.Handle 77 errMode uint32 78} 79 80func (m *master) SetRaw() error { 81 if err := makeInputRaw(m.in, m.inMode); err != nil { 82 return err 83 } 84 85 // Set StdOut and StdErr to raw mode, we ignore failures since 86 // windows.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this version of 87 // Windows. 88 89 windows.SetConsoleMode(m.out, m.outMode|windows.DISABLE_NEWLINE_AUTO_RETURN) 90 91 windows.SetConsoleMode(m.err, m.errMode|windows.DISABLE_NEWLINE_AUTO_RETURN) 92 93 return nil 94} 95 96func (m *master) Reset() error { 97 for _, s := range []struct { 98 fd windows.Handle 99 mode uint32 100 }{ 101 {m.in, m.inMode}, 102 {m.out, m.outMode}, 103 {m.err, m.errMode}, 104 } { 105 if err := windows.SetConsoleMode(s.fd, s.mode); err != nil { 106 return errors.Wrap(err, "unable to restore console mode") 107 } 108 } 109 110 return nil 111} 112 113func (m *master) Size() (WinSize, error) { 114 var info windows.ConsoleScreenBufferInfo 115 err := windows.GetConsoleScreenBufferInfo(m.out, &info) 116 if err != nil { 117 return WinSize{}, errors.Wrap(err, "unable to get console info") 118 } 119 120 winsize := WinSize{ 121 Width: uint16(info.Window.Right - info.Window.Left + 1), 122 Height: uint16(info.Window.Bottom - info.Window.Top + 1), 123 } 124 125 return winsize, nil 126} 127 128func (m *master) Resize(ws WinSize) error { 129 return ErrNotImplemented 130} 131 132func (m *master) ResizeFrom(c Console) error { 133 return ErrNotImplemented 134} 135 136func (m *master) DisableEcho() error { 137 mode := m.inMode &^ windows.ENABLE_ECHO_INPUT 138 mode |= windows.ENABLE_PROCESSED_INPUT 139 mode |= windows.ENABLE_LINE_INPUT 140 141 if err := windows.SetConsoleMode(m.in, mode); err != nil { 142 return errors.Wrap(err, "unable to set console to disable echo") 143 } 144 145 return nil 146} 147 148func (m *master) Close() error { 149 return nil 150} 151 152func (m *master) Read(b []byte) (int, error) { 153 return os.Stdin.Read(b) 154} 155 156func (m *master) Write(b []byte) (int, error) { 157 return os.Stdout.Write(b) 158} 159 160func (m *master) Fd() uintptr { 161 return uintptr(m.in) 162} 163 164// on windows, console can only be made from os.Std{in,out,err}, hence there 165// isnt a single name here we can use. Return a dummy "console" value in this 166// case should be sufficient. 167func (m *master) Name() string { 168 return "console" 169} 170 171// makeInputRaw puts the terminal (Windows Console) connected to the given 172// file descriptor into raw mode 173func makeInputRaw(fd windows.Handle, mode uint32) error { 174 // See 175 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx 176 // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx 177 178 // Disable these modes 179 mode &^= windows.ENABLE_ECHO_INPUT 180 mode &^= windows.ENABLE_LINE_INPUT 181 mode &^= windows.ENABLE_MOUSE_INPUT 182 mode &^= windows.ENABLE_WINDOW_INPUT 183 mode &^= windows.ENABLE_PROCESSED_INPUT 184 185 // Enable these modes 186 mode |= windows.ENABLE_EXTENDED_FLAGS 187 mode |= windows.ENABLE_INSERT_MODE 188 mode |= windows.ENABLE_QUICK_EDIT_MODE 189 190 if vtInputSupported { 191 mode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT 192 } 193 194 if err := windows.SetConsoleMode(fd, mode); err != nil { 195 return errors.Wrap(err, "unable to set console to raw mode") 196 } 197 198 return nil 199} 200 201func checkConsole(f *os.File) error { 202 var mode uint32 203 if err := windows.GetConsoleMode(windows.Handle(f.Fd()), &mode); err != nil { 204 return err 205 } 206 return nil 207} 208 209func newMaster(f *os.File) (Console, error) { 210 if f != os.Stdin && f != os.Stdout && f != os.Stderr { 211 return nil, errors.New("creating a console from a file is not supported on windows") 212 } 213 m := &master{} 214 m.initStdios() 215 return m, nil 216} 217