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