1package liner 2 3import ( 4 "bufio" 5 "os" 6 "syscall" 7 "unsafe" 8) 9 10var ( 11 kernel32 = syscall.NewLazyDLL("kernel32.dll") 12 13 procGetStdHandle = kernel32.NewProc("GetStdHandle") 14 procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW") 15 procGetNumberOfConsoleInputEvents = kernel32.NewProc("GetNumberOfConsoleInputEvents") 16 procGetConsoleMode = kernel32.NewProc("GetConsoleMode") 17 procSetConsoleMode = kernel32.NewProc("SetConsoleMode") 18 procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") 19 procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") 20 procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") 21) 22 23// These names are from the Win32 api, so they use underscores (contrary to 24// what golint suggests) 25const ( 26 std_input_handle = uint32(-10 & 0xFFFFFFFF) 27 std_output_handle = uint32(-11 & 0xFFFFFFFF) 28 std_error_handle = uint32(-12 & 0xFFFFFFFF) 29 invalid_handle_value = ^uintptr(0) 30) 31 32type inputMode uint32 33 34// State represents an open terminal 35type State struct { 36 commonState 37 handle syscall.Handle 38 hOut syscall.Handle 39 origMode inputMode 40 defaultMode inputMode 41 key interface{} 42 repeat uint16 43} 44 45const ( 46 enableEchoInput = 0x4 47 enableInsertMode = 0x20 48 enableLineInput = 0x2 49 enableMouseInput = 0x10 50 enableProcessedInput = 0x1 51 enableQuickEditMode = 0x40 52 enableWindowInput = 0x8 53) 54 55// NewLiner initializes a new *State, and sets the terminal into raw mode. To 56// restore the terminal to its previous state, call State.Close(). 57func NewLiner() *State { 58 var s State 59 hIn, _, _ := procGetStdHandle.Call(uintptr(std_input_handle)) 60 s.handle = syscall.Handle(hIn) 61 hOut, _, _ := procGetStdHandle.Call(uintptr(std_output_handle)) 62 s.hOut = syscall.Handle(hOut) 63 64 s.terminalSupported = true 65 if m, err := TerminalMode(); err == nil { 66 s.origMode = m.(inputMode) 67 mode := s.origMode 68 mode &^= enableEchoInput 69 mode &^= enableInsertMode 70 mode &^= enableLineInput 71 mode &^= enableMouseInput 72 mode |= enableWindowInput 73 mode.ApplyMode() 74 } else { 75 s.inputRedirected = true 76 s.r = bufio.NewReader(os.Stdin) 77 } 78 79 s.getColumns() 80 s.outputRedirected = s.columns <= 0 81 82 return &s 83} 84 85// These names are from the Win32 api, so they use underscores (contrary to 86// what golint suggests) 87const ( 88 focus_event = 0x0010 89 key_event = 0x0001 90 menu_event = 0x0008 91 mouse_event = 0x0002 92 window_buffer_size_event = 0x0004 93) 94 95type input_record struct { 96 eventType uint16 97 pad uint16 98 blob [16]byte 99} 100 101type key_event_record struct { 102 KeyDown int32 103 RepeatCount uint16 104 VirtualKeyCode uint16 105 VirtualScanCode uint16 106 Char int16 107 ControlKeyState uint32 108} 109 110// These names are from the Win32 api, so they use underscores (contrary to 111// what golint suggests) 112const ( 113 vk_tab = 0x09 114 vk_prior = 0x21 115 vk_next = 0x22 116 vk_end = 0x23 117 vk_home = 0x24 118 vk_left = 0x25 119 vk_up = 0x26 120 vk_right = 0x27 121 vk_down = 0x28 122 vk_insert = 0x2d 123 vk_delete = 0x2e 124 vk_f1 = 0x70 125 vk_f2 = 0x71 126 vk_f3 = 0x72 127 vk_f4 = 0x73 128 vk_f5 = 0x74 129 vk_f6 = 0x75 130 vk_f7 = 0x76 131 vk_f8 = 0x77 132 vk_f9 = 0x78 133 vk_f10 = 0x79 134 vk_f11 = 0x7a 135 vk_f12 = 0x7b 136 bKey = 0x42 137 fKey = 0x46 138 yKey = 0x59 139) 140 141const ( 142 shiftPressed = 0x0010 143 leftAltPressed = 0x0002 144 leftCtrlPressed = 0x0008 145 rightAltPressed = 0x0001 146 rightCtrlPressed = 0x0004 147 148 modKeys = shiftPressed | leftAltPressed | rightAltPressed | leftCtrlPressed | rightCtrlPressed 149) 150 151// inputWaiting only returns true if the next call to readNext will return immediately. 152func (s *State) inputWaiting() bool { 153 var num uint32 154 ok, _, _ := procGetNumberOfConsoleInputEvents.Call(uintptr(s.handle), uintptr(unsafe.Pointer(&num))) 155 if ok == 0 { 156 // call failed, so we cannot guarantee a non-blocking readNext 157 return false 158 } 159 160 // during a "paste" input events are always an odd number, and 161 // the last one results in a blocking readNext, so return false 162 // when num is 1 or 0. 163 return num > 1 164} 165 166func (s *State) readNext() (interface{}, error) { 167 if s.repeat > 0 { 168 s.repeat-- 169 return s.key, nil 170 } 171 172 var input input_record 173 pbuf := uintptr(unsafe.Pointer(&input)) 174 var rv uint32 175 prv := uintptr(unsafe.Pointer(&rv)) 176 177 for { 178 ok, _, err := procReadConsoleInput.Call(uintptr(s.handle), pbuf, 1, prv) 179 180 if ok == 0 { 181 return nil, err 182 } 183 184 if input.eventType == window_buffer_size_event { 185 xy := (*coord)(unsafe.Pointer(&input.blob[0])) 186 s.columns = int(xy.x) 187 if s.columns > 1 { 188 s.columns-- 189 } 190 return winch, nil 191 } 192 if input.eventType != key_event { 193 continue 194 } 195 ke := (*key_event_record)(unsafe.Pointer(&input.blob[0])) 196 if ke.KeyDown == 0 { 197 continue 198 } 199 200 if ke.VirtualKeyCode == vk_tab && ke.ControlKeyState&modKeys == shiftPressed { 201 s.key = shiftTab 202 } else if ke.VirtualKeyCode == bKey && (ke.ControlKeyState&modKeys == leftAltPressed || 203 ke.ControlKeyState&modKeys == rightAltPressed) { 204 s.key = altB 205 } else if ke.VirtualKeyCode == fKey && (ke.ControlKeyState&modKeys == leftAltPressed || 206 ke.ControlKeyState&modKeys == rightAltPressed) { 207 s.key = altF 208 } else if ke.VirtualKeyCode == yKey && (ke.ControlKeyState&modKeys == leftAltPressed || 209 ke.ControlKeyState&modKeys == rightAltPressed) { 210 s.key = altY 211 } else if ke.Char > 0 { 212 s.key = rune(ke.Char) 213 } else { 214 switch ke.VirtualKeyCode { 215 case vk_prior: 216 s.key = pageUp 217 case vk_next: 218 s.key = pageDown 219 case vk_end: 220 s.key = end 221 case vk_home: 222 s.key = home 223 case vk_left: 224 s.key = left 225 if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 { 226 if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) { 227 s.key = wordLeft 228 } 229 } 230 case vk_right: 231 s.key = right 232 if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 { 233 if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) { 234 s.key = wordRight 235 } 236 } 237 case vk_up: 238 s.key = up 239 case vk_down: 240 s.key = down 241 case vk_insert: 242 s.key = insert 243 case vk_delete: 244 s.key = del 245 case vk_f1: 246 s.key = f1 247 case vk_f2: 248 s.key = f2 249 case vk_f3: 250 s.key = f3 251 case vk_f4: 252 s.key = f4 253 case vk_f5: 254 s.key = f5 255 case vk_f6: 256 s.key = f6 257 case vk_f7: 258 s.key = f7 259 case vk_f8: 260 s.key = f8 261 case vk_f9: 262 s.key = f9 263 case vk_f10: 264 s.key = f10 265 case vk_f11: 266 s.key = f11 267 case vk_f12: 268 s.key = f12 269 default: 270 // Eat modifier keys 271 // TODO: return Action(Unknown) if the key isn't a 272 // modifier. 273 continue 274 } 275 } 276 277 if ke.RepeatCount > 1 { 278 s.repeat = ke.RepeatCount - 1 279 } 280 return s.key, nil 281 } 282} 283 284// Close returns the terminal to its previous mode 285func (s *State) Close() error { 286 s.origMode.ApplyMode() 287 return nil 288} 289 290func (s *State) startPrompt() { 291 if m, err := TerminalMode(); err == nil { 292 s.defaultMode = m.(inputMode) 293 mode := s.defaultMode 294 mode &^= enableProcessedInput 295 mode.ApplyMode() 296 } 297} 298 299func (s *State) restartPrompt() { 300} 301 302func (s *State) stopPrompt() { 303 s.defaultMode.ApplyMode() 304} 305 306// TerminalSupported returns true because line editing is always 307// supported on Windows. 308func TerminalSupported() bool { 309 return true 310} 311 312func (mode inputMode) ApplyMode() error { 313 hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle)) 314 if hIn == invalid_handle_value || hIn == 0 { 315 return err 316 } 317 ok, _, err := procSetConsoleMode.Call(hIn, uintptr(mode)) 318 if ok != 0 { 319 err = nil 320 } 321 return err 322} 323 324// TerminalMode returns the current terminal input mode as an InputModeSetter. 325// 326// This function is provided for convenience, and should 327// not be necessary for most users of liner. 328func TerminalMode() (ModeApplier, error) { 329 var mode inputMode 330 hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle)) 331 if hIn == invalid_handle_value || hIn == 0 { 332 return nil, err 333 } 334 ok, _, err := procGetConsoleMode.Call(hIn, uintptr(unsafe.Pointer(&mode))) 335 if ok != 0 { 336 err = nil 337 } 338 return mode, err 339} 340