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