1// +build !windows 2 3// The terminal mode manipulation code is derived heavily from: 4// https://github.com/golang/crypto/blob/master/ssh/terminal/util.go: 5// Copyright 2011 The Go Authors. All rights reserved. 6// Use of this source code is governed by a BSD-style 7// license that can be found in the LICENSE file. 8 9package terminal 10 11import ( 12 "bufio" 13 "bytes" 14 "fmt" 15 "syscall" 16 "unsafe" 17) 18 19const ( 20 normalKeypad = '[' 21 applicationKeypad = 'O' 22) 23 24type runeReaderState struct { 25 term syscall.Termios 26 reader *bufio.Reader 27 buf *bytes.Buffer 28} 29 30func newRuneReaderState(input FileReader) runeReaderState { 31 buf := new(bytes.Buffer) 32 return runeReaderState{ 33 reader: bufio.NewReader(&BufferedReader{ 34 In: input, 35 Buffer: buf, 36 }), 37 buf: buf, 38 } 39} 40 41func (rr *RuneReader) Buffer() *bytes.Buffer { 42 return rr.state.buf 43} 44 45// For reading runes we just want to disable echo. 46func (rr *RuneReader) SetTermMode() error { 47 if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 { 48 return err 49 } 50 51 newState := rr.state.term 52 newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG 53 54 if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { 55 return err 56 } 57 58 return nil 59} 60 61func (rr *RuneReader) RestoreTermMode() error { 62 if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 { 63 return err 64 } 65 return nil 66} 67 68// ReadRune Parse escape sequences such as ESC [ A for arrow keys. 69// See https://vt100.net/docs/vt102-ug/appendixc.html 70func (rr *RuneReader) ReadRune() (rune, int, error) { 71 r, size, err := rr.state.reader.ReadRune() 72 if err != nil { 73 return r, size, err 74 } 75 76 if r != KeyEscape { 77 return r, size, err 78 } 79 80 if rr.state.reader.Buffered() == 0 { 81 // no more characters so must be `Esc` key 82 return KeyEscape, 1, nil 83 } 84 85 r, size, err = rr.state.reader.ReadRune() 86 if err != nil { 87 return r, size, err 88 } 89 90 // ESC O ... or ESC [ ...? 91 if r != normalKeypad && r != applicationKeypad { 92 return r, size, fmt.Errorf("unexpected escape sequence from terminal: %q", []rune{KeyEscape, r}) 93 } 94 95 keypad := r 96 97 r, size, err = rr.state.reader.ReadRune() 98 if err != nil { 99 return r, size, err 100 } 101 102 switch r { 103 case 'A': // ESC [ A or ESC O A 104 return KeyArrowUp, 1, nil 105 case 'B': // ESC [ B or ESC O B 106 return KeyArrowDown, 1, nil 107 case 'C': // ESC [ C or ESC O C 108 return KeyArrowRight, 1, nil 109 case 'D': // ESC [ D or ESC O D 110 return KeyArrowLeft, 1, nil 111 case 'F': // ESC [ F or ESC O F 112 return SpecialKeyEnd, 1, nil 113 case 'H': // ESC [ H or ESC O H 114 return SpecialKeyHome, 1, nil 115 case '3': // ESC [ 3 116 if keypad == normalKeypad { 117 // discard the following '~' key from buffer 118 _, _ = rr.state.reader.Discard(1) 119 return SpecialKeyDelete, 1, nil 120 } 121 } 122 123 // discard the following '~' key from buffer 124 _, _ = rr.state.reader.Discard(1) 125 return IgnoreKey, 1, nil 126} 127