1// +build !windows
2
3package termbox
4
5import "unicode/utf8"
6import "bytes"
7import "syscall"
8import "unsafe"
9import "strings"
10import "strconv"
11import "os"
12import "io"
13
14// private API
15
16const (
17	t_enter_ca = iota
18	t_exit_ca
19	t_show_cursor
20	t_hide_cursor
21	t_clear_screen
22	t_sgr0
23	t_underline
24	t_bold
25	t_blink
26	t_reverse
27	t_enter_keypad
28	t_exit_keypad
29	t_enter_mouse
30	t_exit_mouse
31	t_max_funcs
32)
33
34const (
35	coord_invalid = -2
36	attr_invalid  = Attribute(0xFFFF)
37)
38
39type input_event struct {
40	data []byte
41	err  error
42}
43
44var (
45	// term specific sequences
46	keys  []string
47	funcs []string
48
49	// termbox inner state
50	orig_tios      syscall_Termios
51	back_buffer    cellbuf
52	front_buffer   cellbuf
53	termw          int
54	termh          int
55	input_mode     = InputEsc
56	output_mode    = OutputNormal
57	out            *os.File
58	in             int
59	lastfg         = attr_invalid
60	lastbg         = attr_invalid
61	lastx          = coord_invalid
62	lasty          = coord_invalid
63	cursor_x       = cursor_hidden
64	cursor_y       = cursor_hidden
65	foreground     = ColorDefault
66	background     = ColorDefault
67	inbuf          = make([]byte, 0, 64)
68	outbuf         bytes.Buffer
69	sigwinch       = make(chan os.Signal, 1)
70	sigio          = make(chan os.Signal, 1)
71	quit           = make(chan int)
72	input_comm     = make(chan input_event)
73	interrupt_comm = make(chan struct{})
74	intbuf         = make([]byte, 0, 16)
75
76	// grayscale indexes
77	grayscale = []Attribute{
78		0, 17, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244,
79		245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 232,
80	}
81)
82
83func write_cursor(x, y int) {
84	outbuf.WriteString("\033[")
85	outbuf.Write(strconv.AppendUint(intbuf, uint64(y+1), 10))
86	outbuf.WriteString(";")
87	outbuf.Write(strconv.AppendUint(intbuf, uint64(x+1), 10))
88	outbuf.WriteString("H")
89}
90
91func write_sgr_fg(a Attribute) {
92	switch output_mode {
93	case Output256, Output216, OutputGrayscale:
94		outbuf.WriteString("\033[38;5;")
95		outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
96		outbuf.WriteString("m")
97	default:
98		outbuf.WriteString("\033[3")
99		outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
100		outbuf.WriteString("m")
101	}
102}
103
104func write_sgr_bg(a Attribute) {
105	switch output_mode {
106	case Output256, Output216, OutputGrayscale:
107		outbuf.WriteString("\033[48;5;")
108		outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
109		outbuf.WriteString("m")
110	default:
111		outbuf.WriteString("\033[4")
112		outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
113		outbuf.WriteString("m")
114	}
115}
116
117func write_sgr(fg, bg Attribute) {
118	switch output_mode {
119	case Output256, Output216, OutputGrayscale:
120		outbuf.WriteString("\033[38;5;")
121		outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
122		outbuf.WriteString("m")
123		outbuf.WriteString("\033[48;5;")
124		outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
125		outbuf.WriteString("m")
126	default:
127		outbuf.WriteString("\033[3")
128		outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
129		outbuf.WriteString(";4")
130		outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
131		outbuf.WriteString("m")
132	}
133}
134
135type winsize struct {
136	rows    uint16
137	cols    uint16
138	xpixels uint16
139	ypixels uint16
140}
141
142func get_term_size(fd uintptr) (int, int) {
143	var sz winsize
144	_, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
145		fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
146	return int(sz.cols), int(sz.rows)
147}
148
149func send_attr(fg, bg Attribute) {
150	if fg == lastfg && bg == lastbg {
151		return
152	}
153
154	outbuf.WriteString(funcs[t_sgr0])
155
156	var fgcol, bgcol Attribute
157
158	switch output_mode {
159	case Output256:
160		fgcol = fg & 0x1FF
161		bgcol = bg & 0x1FF
162	case Output216:
163		fgcol = fg & 0xFF
164		bgcol = bg & 0xFF
165		if fgcol > 216 {
166			fgcol = ColorDefault
167		}
168		if bgcol > 216 {
169			bgcol = ColorDefault
170		}
171		if fgcol != ColorDefault {
172			fgcol += 0x10
173		}
174		if bgcol != ColorDefault {
175			bgcol += 0x10
176		}
177	case OutputGrayscale:
178		fgcol = fg & 0x1F
179		bgcol = bg & 0x1F
180		if fgcol > 26 {
181			fgcol = ColorDefault
182		}
183		if bgcol > 26 {
184			bgcol = ColorDefault
185		}
186		if fgcol != ColorDefault {
187			fgcol = grayscale[fgcol]
188		}
189		if bgcol != ColorDefault {
190			bgcol = grayscale[bgcol]
191		}
192	default:
193		fgcol = fg & 0x0F
194		bgcol = bg & 0x0F
195	}
196
197	if fgcol != ColorDefault {
198		if bgcol != ColorDefault {
199			write_sgr(fgcol, bgcol)
200		} else {
201			write_sgr_fg(fgcol)
202		}
203	} else if bgcol != ColorDefault {
204		write_sgr_bg(bgcol)
205	}
206
207	if fg&AttrBold != 0 {
208		outbuf.WriteString(funcs[t_bold])
209	}
210	if bg&AttrBold != 0 {
211		outbuf.WriteString(funcs[t_blink])
212	}
213	if fg&AttrUnderline != 0 {
214		outbuf.WriteString(funcs[t_underline])
215	}
216	if fg&AttrReverse|bg&AttrReverse != 0 {
217		outbuf.WriteString(funcs[t_reverse])
218	}
219
220	lastfg, lastbg = fg, bg
221}
222
223func send_char(x, y int, ch rune) {
224	var buf [8]byte
225	n := utf8.EncodeRune(buf[:], ch)
226	if x-1 != lastx || y != lasty {
227		write_cursor(x, y)
228	}
229	lastx, lasty = x, y
230	outbuf.Write(buf[:n])
231}
232
233func flush() error {
234	_, err := io.Copy(out, &outbuf)
235	outbuf.Reset()
236	return err
237}
238
239func send_clear() error {
240	send_attr(foreground, background)
241	outbuf.WriteString(funcs[t_clear_screen])
242	if !is_cursor_hidden(cursor_x, cursor_y) {
243		write_cursor(cursor_x, cursor_y)
244	}
245
246	// we need to invalidate cursor position too and these two vars are
247	// used only for simple cursor positioning optimization, cursor
248	// actually may be in the correct place, but we simply discard
249	// optimization once and it gives us simple solution for the case when
250	// cursor moved
251	lastx = coord_invalid
252	lasty = coord_invalid
253
254	return flush()
255}
256
257func update_size_maybe() error {
258	w, h := get_term_size(out.Fd())
259	if w != termw || h != termh {
260		termw, termh = w, h
261		back_buffer.resize(termw, termh)
262		front_buffer.resize(termw, termh)
263		front_buffer.clear()
264		return send_clear()
265	}
266	return nil
267}
268
269func tcsetattr(fd uintptr, termios *syscall_Termios) error {
270	r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
271		fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios)))
272	if r != 0 {
273		return os.NewSyscallError("SYS_IOCTL", e)
274	}
275	return nil
276}
277
278func tcgetattr(fd uintptr, termios *syscall_Termios) error {
279	r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
280		fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios)))
281	if r != 0 {
282		return os.NewSyscallError("SYS_IOCTL", e)
283	}
284	return nil
285}
286
287func parse_mouse_event(event *Event, buf string) (int, bool) {
288	if strings.HasPrefix(buf, "\033[M") && len(buf) >= 6 {
289		// X10 mouse encoding, the simplest one
290		// \033 [ M Cb Cx Cy
291		b := buf[3] - 32
292		switch b & 3 {
293		case 0:
294			if b&64 != 0 {
295				event.Key = MouseWheelUp
296			} else {
297				event.Key = MouseLeft
298			}
299		case 1:
300			if b&64 != 0 {
301				event.Key = MouseWheelDown
302			} else {
303				event.Key = MouseMiddle
304			}
305		case 2:
306			event.Key = MouseRight
307		case 3:
308			event.Key = MouseRelease
309		default:
310			return 6, false
311		}
312		event.Type = EventMouse // KeyEvent by default
313		if b&32 != 0 {
314			event.Mod |= ModMotion
315		}
316
317		// the coord is 1,1 for upper left
318		event.MouseX = int(buf[4]) - 1 - 32
319		event.MouseY = int(buf[5]) - 1 - 32
320		return 6, true
321	} else if strings.HasPrefix(buf, "\033[<") || strings.HasPrefix(buf, "\033[") {
322		// xterm 1006 extended mode or urxvt 1015 extended mode
323		// xterm: \033 [ < Cb ; Cx ; Cy (M or m)
324		// urxvt: \033 [ Cb ; Cx ; Cy M
325
326		// find the first M or m, that's where we stop
327		mi := strings.IndexAny(buf, "Mm")
328		if mi == -1 {
329			return 0, false
330		}
331
332		// whether it's a capital M or not
333		isM := buf[mi] == 'M'
334
335		// whether it's urxvt or not
336		isU := false
337
338		// buf[2] is safe here, because having M or m found means we have at
339		// least 3 bytes in a string
340		if buf[2] == '<' {
341			buf = buf[3:mi]
342		} else {
343			isU = true
344			buf = buf[2:mi]
345		}
346
347		s1 := strings.Index(buf, ";")
348		s2 := strings.LastIndex(buf, ";")
349		// not found or only one ';'
350		if s1 == -1 || s2 == -1 || s1 == s2 {
351			return 0, false
352		}
353
354		n1, err := strconv.ParseInt(buf[0:s1], 10, 64)
355		if err != nil {
356			return 0, false
357		}
358		n2, err := strconv.ParseInt(buf[s1+1:s2], 10, 64)
359		if err != nil {
360			return 0, false
361		}
362		n3, err := strconv.ParseInt(buf[s2+1:], 10, 64)
363		if err != nil {
364			return 0, false
365		}
366
367		// on urxvt, first number is encoded exactly as in X10, but we need to
368		// make it zero-based, on xterm it is zero-based already
369		if isU {
370			n1 -= 32
371		}
372		switch n1 & 3 {
373		case 0:
374			if n1&64 != 0 {
375				event.Key = MouseWheelUp
376			} else {
377				event.Key = MouseLeft
378			}
379		case 1:
380			if n1&64 != 0 {
381				event.Key = MouseWheelDown
382			} else {
383				event.Key = MouseMiddle
384			}
385		case 2:
386			event.Key = MouseRight
387		case 3:
388			event.Key = MouseRelease
389		default:
390			return mi + 1, false
391		}
392		if !isM {
393			// on xterm mouse release is signaled by lowercase m
394			event.Key = MouseRelease
395		}
396
397		event.Type = EventMouse // KeyEvent by default
398		if n1&32 != 0 {
399			event.Mod |= ModMotion
400		}
401
402		event.MouseX = int(n2) - 1
403		event.MouseY = int(n3) - 1
404		return mi + 1, true
405	}
406
407	return 0, false
408}
409
410func parse_escape_sequence(event *Event, buf []byte) (int, bool) {
411	bufstr := string(buf)
412	for i, key := range keys {
413		if strings.HasPrefix(bufstr, key) {
414			event.Ch = 0
415			event.Key = Key(0xFFFF - i)
416			return len(key), true
417		}
418	}
419
420	// if none of the keys match, let's try mouse seqences
421	return parse_mouse_event(event, bufstr)
422}
423
424func extract_raw_event(data []byte, event *Event) bool {
425	if len(inbuf) == 0 {
426		return false
427	}
428
429	n := len(data)
430	if n == 0 {
431		return false
432	}
433
434	n = copy(data, inbuf)
435	copy(inbuf, inbuf[n:])
436	inbuf = inbuf[:len(inbuf)-n]
437
438	event.N = n
439	event.Type = EventRaw
440	return true
441}
442
443func extract_event(inbuf []byte, event *Event) bool {
444	if len(inbuf) == 0 {
445		event.N = 0
446		return false
447	}
448
449	if inbuf[0] == '\033' {
450		// possible escape sequence
451		if n, ok := parse_escape_sequence(event, inbuf); n != 0 {
452			event.N = n
453			return ok
454		}
455
456		// it's not escape sequence, then it's Alt or Esc, check input_mode
457		switch {
458		case input_mode&InputEsc != 0:
459			// if we're in escape mode, fill Esc event, pop buffer, return success
460			event.Ch = 0
461			event.Key = KeyEsc
462			event.Mod = 0
463			event.N = 1
464			return true
465		case input_mode&InputAlt != 0:
466			// if we're in alt mode, set Alt modifier to event and redo parsing
467			event.Mod = ModAlt
468			ok := extract_event(inbuf[1:], event)
469			if ok {
470				event.N++
471			} else {
472				event.N = 0
473			}
474			return ok
475		default:
476			panic("unreachable")
477		}
478	}
479
480	// if we're here, this is not an escape sequence and not an alt sequence
481	// so, it's a FUNCTIONAL KEY or a UNICODE character
482
483	// first of all check if it's a functional key
484	if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 {
485		// fill event, pop buffer, return success
486		event.Ch = 0
487		event.Key = Key(inbuf[0])
488		event.N = 1
489		return true
490	}
491
492	// the only possible option is utf8 rune
493	if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
494		event.Ch = r
495		event.Key = 0
496		event.N = n
497		return true
498	}
499
500	return false
501}
502
503func fcntl(fd int, cmd int, arg int) (val int, err error) {
504	r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd),
505		uintptr(arg))
506	val = int(r)
507	if e != 0 {
508		err = e
509	}
510	return
511}
512