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