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