1// +build !windows 2 3package vt100 4 5import ( 6 "strconv" 7 "time" 8 "unicode" 9 10 "github.com/pkg/term" 11) 12 13var ( 14 defaultTimeout = 2 * time.Millisecond 15 lastKey int 16) 17 18type TTY struct { 19 t *term.Term 20 timeout time.Duration 21} 22 23// NewTTY opens /dev/tty in raw and cbreak mode as a term.Term 24func NewTTY() (*TTY, error) { 25 t, err := term.Open("/dev/tty", term.RawMode, term.CBreakMode, term.ReadTimeout(defaultTimeout)) 26 if err != nil { 27 return nil, err 28 } 29 return &TTY{t, defaultTimeout}, nil 30} 31 32// Term will return the underlying term.Term 33func (tty *TTY) Term() *term.Term { 34 return tty.t 35} 36 37// RawMode will switch the terminal to raw mode 38func (tty *TTY) RawMode() { 39 term.RawMode(tty.t) 40} 41 42// NoBlock leaves "cooked" mode and enters "cbreak" mode 43func (tty *TTY) NoBlock() { 44 tty.t.SetCbreak() 45} 46 47// SetTimeout sets a timeout for reading a key 48func (tty *TTY) SetTimeout(d time.Duration) { 49 tty.timeout = d 50 tty.t.SetReadTimeout(tty.timeout) 51} 52 53// Restore will restore the terminal 54func (tty *TTY) Restore() { 55 tty.t.Restore() 56} 57 58// Close will Restore and close the raw terminal 59func (tty *TTY) Close() { 60 t := tty.Term() 61 t.Restore() 62 t.Close() 63} 64 65// Thanks https://stackoverflow.com/a/32018700/131264 66// Returns either an ascii code, or (if input is an arrow) a Javascript key code. 67func asciiAndKeyCode(tty *TTY) (ascii, keyCode int, err error) { 68 bytes := make([]byte, 3) 69 var numRead int 70 tty.RawMode() 71 tty.NoBlock() 72 tty.SetTimeout(tty.timeout) 73 numRead, err = tty.t.Read(bytes) 74 tty.Restore() 75 tty.t.Flush() 76 if err != nil { 77 return 78 } 79 if numRead == 3 && bytes[0] == 27 && bytes[1] == 91 { 80 // Three-character control sequence, beginning with "ESC-[". 81 82 // Since there are no ASCII codes for arrow keys, we use 83 // Javascript key codes. 84 if bytes[2] == 65 { 85 // Up 86 keyCode = 38 87 } else if bytes[2] == 66 { 88 // Down 89 keyCode = 40 90 } else if bytes[2] == 67 { 91 // Right 92 keyCode = 39 93 } else if bytes[2] == 68 { 94 // Left 95 keyCode = 37 96 } 97 } else if numRead == 1 { 98 ascii = int(bytes[0]) 99 //} else { 100 // TWo characters read?? 101 } 102 return 103} 104 105// Don't use the "JavaScript key codes" for the arrow keys 106func asciiAndKeyCodeNoJavascript(tty *TTY) (ascii, keyCode int, err error) { 107 bytes := make([]byte, 3) 108 var numRead int 109 tty.RawMode() 110 tty.NoBlock() 111 tty.SetTimeout(tty.timeout) 112 numRead, err = tty.t.Read(bytes) 113 tty.Restore() 114 tty.t.Flush() 115 if err != nil { 116 return 117 } 118 if numRead == 3 && bytes[0] == 27 && bytes[1] == 91 { 119 // Three-character control sequence, beginning with "ESC-[". 120 121 // Since there are no ASCII codes for arrow keys, we use 122 // the last 4 values of a byte 123 if bytes[2] == 65 { 124 // Up 125 keyCode = 253 126 } else if bytes[2] == 66 { 127 // Down 128 keyCode = 255 129 } else if bytes[2] == 67 { 130 // Right 131 keyCode = 254 132 } else if bytes[2] == 68 { 133 // Left 134 keyCode = 252 135 } 136 } else if numRead == 1 { 137 ascii = int(bytes[0]) 138 //} else { 139 // Two characters read?? 140 } 141 return 142} 143 144// Returns either an ascii code, or (if input is an arrow) a Javascript key code. 145func asciiAndKeyCodeOnce() (ascii, keyCode int, err error) { 146 t, err := NewTTY() 147 if err != nil { 148 return 0, 0, err 149 } 150 a, kc, err := asciiAndKeyCode(t) 151 t.Close() 152 return a, kc, err 153} 154 155func (tty *TTY) ASCII() int { 156 ascii, _, err := asciiAndKeyCode(tty) 157 if err != nil { 158 return 0 159 } 160 return ascii 161} 162 163func ASCIIOnce() int { 164 ascii, _, err := asciiAndKeyCodeOnce() 165 if err != nil { 166 return 0 167 } 168 return ascii 169} 170 171func (tty *TTY) KeyCode() int { 172 _, keyCode, err := asciiAndKeyCode(tty) 173 if err != nil { 174 return 0 175 } 176 return keyCode 177} 178 179func KeyCodeOnce() int { 180 _, keyCode, err := asciiAndKeyCodeOnce() 181 if err != nil { 182 return 0 183 } 184 return keyCode 185} 186 187// Return the keyCode or ascii, but ignore repeated keys 188func (tty *TTY) Key() int { 189 ascii, keyCode, err := asciiAndKeyCodeNoJavascript(tty) 190 if err != nil { 191 lastKey = 0 192 return 0 193 } 194 if keyCode != 0 { 195 if keyCode == lastKey { 196 lastKey = 0 197 return 0 198 } 199 lastKey = keyCode 200 return keyCode 201 } 202 if ascii == lastKey { 203 lastKey = 0 204 return 0 205 } 206 lastKey = ascii 207 return ascii 208} 209 210func KeyOnce() int { 211 ascii, keyCode, err := asciiAndKeyCodeOnce() 212 if err != nil { 213 return 0 214 } 215 if keyCode != 0 { 216 return keyCode 217 } 218 return ascii 219} 220 221// Wait for Esc, Enter or Space to be pressed 222func WaitForKey() { 223 // Get a new TTY and start reading keypresses in a loop 224 r, err := NewTTY() 225 if err != nil { 226 r.Close() 227 panic(err) 228 } 229 //r.SetTimeout(10 * time.Millisecond) 230 for { 231 switch r.Key() { 232 case 27, 13, 32: 233 r.Close() 234 return 235 } 236 } 237} 238 239// String will block and then return a string 240// Arrow keys are returned as ←, →, ↑ or ↓ 241// returns an empty string if the pressed key could not be interpreted 242func (tty *TTY) String() string { 243 bytes := make([]byte, 3) 244 tty.RawMode() 245 //tty.NoBlock() 246 tty.SetTimeout(0) 247 numRead, err := tty.t.Read(bytes) 248 if err != nil { 249 return "" 250 } 251 tty.Restore() 252 tty.t.Flush() 253 if numRead == 3 && bytes[0] == 27 && bytes[1] == 91 { 254 // Three-character control sequence, beginning with "ESC-[". 255 256 // Since there are no ASCII codes for arrow keys, we use 257 // the last 4 values of a byte 258 if bytes[2] == 65 { 259 // Up 260 return "↑" 261 } else if bytes[2] == 66 { 262 // Down 263 return "↓" 264 } else if bytes[2] == 67 { 265 // Right 266 return "→" 267 } else if bytes[2] == 68 { 268 // Left 269 return "←" 270 } 271 } else if numRead == 1 { 272 r := rune(bytes[0]) 273 if unicode.IsPrint(r) { 274 return string(r) 275 } 276 return "c:" + strconv.Itoa(int(r)) 277 } else { 278 // Two or more bytes, a unicode character (or mashing several keys) 279 return string([]rune(string(bytes))[0]) 280 } 281 return "" 282} 283 284// Rune will block and then return a rune. 285// Arrow keys are returned as ←, →, ↑ or ↓ 286// returns a rune(0) if the pressed key could not be interpreted 287func (tty *TTY) Rune() rune { 288 bytes := make([]byte, 3) 289 tty.RawMode() 290 //tty.NoBlock() 291 tty.SetTimeout(0) 292 numRead, err := tty.t.Read(bytes) 293 if err != nil { 294 return rune(0) 295 } 296 tty.Restore() 297 tty.t.Flush() 298 if numRead == 3 && bytes[0] == 27 && bytes[1] == 91 { 299 // Three-character control sequence, beginning with "ESC-[". 300 301 // Since there are no ASCII codes for arrow keys, we use 302 // the last 4 values of a byte 303 if bytes[2] == 65 { 304 // Up 305 return '↑' 306 } else if bytes[2] == 66 { 307 // Down 308 return '↓' 309 } else if bytes[2] == 67 { 310 // Right 311 return '→' 312 } else if bytes[2] == 68 { 313 // Left 314 return '←' 315 } 316 } else if numRead == 1 { 317 return rune(bytes[0]) 318 } else { 319 // Two or more bytes, a unicode character (or mashing several keys) 320 return []rune(string(bytes))[0] 321 } 322 return rune(0) 323} 324