1// +build !windows
2
3package termbox
4
5import (
6	"fmt"
7	"os"
8	"os/signal"
9	"runtime"
10	"syscall"
11	"time"
12
13	"github.com/mattn/go-runewidth"
14)
15
16// public API
17
18// Initializes termbox library. This function should be called before any other functions.
19// After successful initialization, the library must be finalized using 'Close' function.
20//
21// Example usage:
22//      err := termbox.Init()
23//      if err != nil {
24//              panic(err)
25//      }
26//      defer termbox.Close()
27func Init() error {
28	if IsInit {
29		return nil
30	}
31
32	var err error
33
34	if runtime.GOOS == "openbsd" || runtime.GOOS == "freebsd" {
35		out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
36		if err != nil {
37			return err
38		}
39		in = int(out.Fd())
40	} else {
41		out, err = os.OpenFile("/dev/tty", os.O_WRONLY, 0)
42		if err != nil {
43			return err
44		}
45		in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
46		if err != nil {
47			return err
48		}
49	}
50
51	err = setup_term()
52	if err != nil {
53		return fmt.Errorf("termbox: error while reading terminfo data: %v", err)
54	}
55
56	signal.Notify(sigwinch, syscall.SIGWINCH)
57	signal.Notify(sigio, syscall.SIGIO)
58
59	_, err = fcntl(in, syscall.F_SETFL, syscall.O_ASYNC|syscall.O_NONBLOCK)
60	if err != nil {
61		return err
62	}
63	_, err = fcntl(in, syscall.F_SETOWN, syscall.Getpid())
64	if runtime.GOOS != "darwin" && err != nil {
65		return err
66	}
67	err = tcgetattr(out.Fd(), &orig_tios)
68	if err != nil {
69		return err
70	}
71
72	tios := orig_tios
73	tios.Iflag &^= syscall_IGNBRK | syscall_BRKINT | syscall_PARMRK |
74		syscall_ISTRIP | syscall_INLCR | syscall_IGNCR |
75		syscall_ICRNL | syscall_IXON
76	tios.Lflag &^= syscall_ECHO | syscall_ECHONL | syscall_ICANON |
77		syscall_ISIG | syscall_IEXTEN
78	tios.Cflag &^= syscall_CSIZE | syscall_PARENB
79	tios.Cflag |= syscall_CS8
80	tios.Cc[syscall_VMIN] = 1
81	tios.Cc[syscall_VTIME] = 0
82
83	err = tcsetattr(out.Fd(), &tios)
84	if err != nil {
85		return err
86	}
87
88	out.WriteString(funcs[t_enter_ca])
89	out.WriteString(funcs[t_enter_keypad])
90	out.WriteString(funcs[t_hide_cursor])
91	out.WriteString(funcs[t_clear_screen])
92
93	termw, termh = get_term_size(out.Fd())
94	back_buffer.init(termw, termh)
95	front_buffer.init(termw, termh)
96	back_buffer.clear()
97	front_buffer.clear()
98
99	go func() {
100		buf := make([]byte, 128)
101		for {
102			select {
103			case <-sigio:
104				for {
105					n, err := syscall.Read(in, buf)
106					if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK {
107						break
108					}
109					select {
110					case input_comm <- input_event{buf[:n], err}:
111						ie := <-input_comm
112						buf = ie.data[:128]
113					case <-quit:
114						return
115					}
116				}
117			case <-quit:
118				return
119			}
120		}
121	}()
122
123	IsInit = true
124	return nil
125}
126
127// Interrupt an in-progress call to PollEvent by causing it to return
128// EventInterrupt.  Note that this function will block until the PollEvent
129// function has successfully been interrupted.
130func Interrupt() {
131	interrupt_comm <- struct{}{}
132}
133
134// Finalizes termbox library, should be called after successful initialization
135// when termbox's functionality isn't required anymore.
136func Close() {
137	if !IsInit {
138		return
139	}
140
141	quit <- 1
142	out.WriteString(funcs[t_show_cursor])
143	out.WriteString(funcs[t_sgr0])
144	out.WriteString(funcs[t_clear_screen])
145	out.WriteString(funcs[t_exit_ca])
146	out.WriteString(funcs[t_exit_keypad])
147	out.WriteString(funcs[t_exit_mouse])
148	tcsetattr(out.Fd(), &orig_tios)
149
150	out.Close()
151	syscall.Close(in)
152
153	// reset the state, so that on next Init() it will work again
154	termw = 0
155	termh = 0
156	input_mode = InputEsc
157	out = nil
158	in = 0
159	lastfg = attr_invalid
160	lastbg = attr_invalid
161	lastx = coord_invalid
162	lasty = coord_invalid
163	cursor_x = cursor_hidden
164	cursor_y = cursor_hidden
165	foreground = ColorDefault
166	background = ColorDefault
167	IsInit = false
168}
169
170// Synchronizes the internal back buffer with the terminal.
171func Flush() error {
172	// invalidate cursor position
173	lastx = coord_invalid
174	lasty = coord_invalid
175
176	update_size_maybe()
177
178	for y := 0; y < front_buffer.height; y++ {
179		line_offset := y * front_buffer.width
180		for x := 0; x < front_buffer.width; {
181			cell_offset := line_offset + x
182			back := &back_buffer.cells[cell_offset]
183			front := &front_buffer.cells[cell_offset]
184			if back.Ch < ' ' {
185				back.Ch = ' '
186			}
187			w := runewidth.RuneWidth(back.Ch)
188			if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) {
189				w = 1
190			}
191			if *back == *front {
192				x += w
193				continue
194			}
195			*front = *back
196			send_attr(back.Fg, back.Bg)
197
198			if w == 2 && x == front_buffer.width-1 {
199				// there's not enough space for 2-cells rune,
200				// let's just put a space in there
201				send_char(x, y, ' ')
202			} else {
203				send_char(x, y, back.Ch)
204				if w == 2 {
205					next := cell_offset + 1
206					front_buffer.cells[next] = Cell{
207						Ch: 0,
208						Fg: back.Fg,
209						Bg: back.Bg,
210					}
211				}
212			}
213			x += w
214		}
215	}
216	if !is_cursor_hidden(cursor_x, cursor_y) {
217		write_cursor(cursor_x, cursor_y)
218	}
219	return flush()
220}
221
222// Sets the position of the cursor. See also HideCursor().
223func SetCursor(x, y int) {
224	if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) {
225		outbuf.WriteString(funcs[t_show_cursor])
226	}
227
228	if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) {
229		outbuf.WriteString(funcs[t_hide_cursor])
230	}
231
232	cursor_x, cursor_y = x, y
233	if !is_cursor_hidden(cursor_x, cursor_y) {
234		write_cursor(cursor_x, cursor_y)
235	}
236}
237
238// The shortcut for SetCursor(-1, -1).
239func HideCursor() {
240	SetCursor(cursor_hidden, cursor_hidden)
241}
242
243// Changes cell's parameters in the internal back buffer at the specified
244// position.
245func SetCell(x, y int, ch rune, fg, bg Attribute) {
246	if x < 0 || x >= back_buffer.width {
247		return
248	}
249	if y < 0 || y >= back_buffer.height {
250		return
251	}
252
253	back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
254}
255
256// Returns a slice into the termbox's back buffer. You can get its dimensions
257// using 'Size' function. The slice remains valid as long as no 'Clear' or
258// 'Flush' function calls were made after call to this function.
259func CellBuffer() []Cell {
260	return back_buffer.cells
261}
262
263// After getting a raw event from PollRawEvent function call, you can parse it
264// again into an ordinary one using termbox logic. That is parse an event as
265// termbox would do it. Returned event in addition to usual Event struct fields
266// sets N field to the amount of bytes used within 'data' slice. If the length
267// of 'data' slice is zero or event cannot be parsed for some other reason, the
268// function will return a special event type: EventNone.
269//
270// IMPORTANT: EventNone may contain a non-zero N, which means you should skip
271// these bytes, because termbox cannot recognize them.
272//
273// NOTE: This API is experimental and may change in future.
274func ParseEvent(data []byte) Event {
275	event := Event{Type: EventKey}
276	status := extract_event(data, &event, false)
277	if status != event_extracted {
278		return Event{Type: EventNone, N: event.N}
279	}
280	return event
281}
282
283// Wait for an event and return it. This is a blocking function call. Instead
284// of EventKey and EventMouse it returns EventRaw events. Raw event is written
285// into `data` slice and Event's N field is set to the amount of bytes written.
286// The minimum required length of the 'data' slice is 1. This requirement may
287// vary on different platforms.
288//
289// NOTE: This API is experimental and may change in future.
290func PollRawEvent(data []byte) Event {
291	if len(data) == 0 {
292		panic("len(data) >= 1 is a requirement")
293	}
294
295	var event Event
296	if extract_raw_event(data, &event) {
297		return event
298	}
299
300	for {
301		select {
302		case ev := <-input_comm:
303			if ev.err != nil {
304				return Event{Type: EventError, Err: ev.err}
305			}
306
307			inbuf = append(inbuf, ev.data...)
308			input_comm <- ev
309			if extract_raw_event(data, &event) {
310				return event
311			}
312		case <-interrupt_comm:
313			event.Type = EventInterrupt
314			return event
315
316		case <-sigwinch:
317			event.Type = EventResize
318			event.Width, event.Height = get_term_size(out.Fd())
319			return event
320		}
321	}
322}
323
324// Wait for an event and return it. This is a blocking function call.
325func PollEvent() Event {
326	// Constant governing macOS specific behavior. See https://github.com/nsf/termbox-go/issues/132
327	// This is an arbitrary delay which hopefully will be enough time for any lagging
328	// partial escape sequences to come through.
329	const esc_wait_delay = 100 * time.Millisecond
330
331	var event Event
332	var esc_wait_timer *time.Timer
333	var esc_timeout <-chan time.Time
334
335	// try to extract event from input buffer, return on success
336	event.Type = EventKey
337	status := extract_event(inbuf, &event, true)
338	if event.N != 0 {
339		copy(inbuf, inbuf[event.N:])
340		inbuf = inbuf[:len(inbuf)-event.N]
341	}
342	if status == event_extracted {
343		return event
344	} else if status == esc_wait {
345		esc_wait_timer = time.NewTimer(esc_wait_delay)
346		esc_timeout = esc_wait_timer.C
347	}
348
349	for {
350		select {
351		case ev := <-input_comm:
352			if esc_wait_timer != nil {
353				if !esc_wait_timer.Stop() {
354					<-esc_wait_timer.C
355				}
356				esc_wait_timer = nil
357			}
358
359			if ev.err != nil {
360				return Event{Type: EventError, Err: ev.err}
361			}
362
363			inbuf = append(inbuf, ev.data...)
364			input_comm <- ev
365			status := extract_event(inbuf, &event, true)
366			if event.N != 0 {
367				copy(inbuf, inbuf[event.N:])
368				inbuf = inbuf[:len(inbuf)-event.N]
369			}
370			if status == event_extracted {
371				return event
372			} else if status == esc_wait {
373				esc_wait_timer = time.NewTimer(esc_wait_delay)
374				esc_timeout = esc_wait_timer.C
375			}
376		case <-esc_timeout:
377			esc_wait_timer = nil
378
379			status := extract_event(inbuf, &event, false)
380			if event.N != 0 {
381				copy(inbuf, inbuf[event.N:])
382				inbuf = inbuf[:len(inbuf)-event.N]
383			}
384			if status == event_extracted {
385				return event
386			}
387		case <-interrupt_comm:
388			event.Type = EventInterrupt
389			return event
390
391		case <-sigwinch:
392			event.Type = EventResize
393			event.Width, event.Height = get_term_size(out.Fd())
394			return event
395		}
396	}
397}
398
399// Returns the size of the internal back buffer (which is mostly the same as
400// terminal's window size in characters). But it doesn't always match the size
401// of the terminal window, after the terminal size has changed, the internal
402// back buffer will get in sync only after Clear or Flush function calls.
403func Size() (width int, height int) {
404	return termw, termh
405}
406
407// Clears the internal back buffer.
408func Clear(fg, bg Attribute) error {
409	foreground, background = fg, bg
410	err := update_size_maybe()
411	back_buffer.clear()
412	return err
413}
414
415// Sets termbox input mode. Termbox has two input modes:
416//
417// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match
418// any known sequence. ESC means KeyEsc. This is the default input mode.
419//
420// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match
421// any known sequence. ESC enables ModAlt modifier for the next keyboard event.
422//
423// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will
424// enable mouse button press/release and drag events.
425//
426// If 'mode' is InputCurrent, returns the current input mode. See also Input*
427// constants.
428func SetInputMode(mode InputMode) InputMode {
429	if mode == InputCurrent {
430		return input_mode
431	}
432	if mode&(InputEsc|InputAlt) == 0 {
433		mode |= InputEsc
434	}
435	if mode&(InputEsc|InputAlt) == InputEsc|InputAlt {
436		mode &^= InputAlt
437	}
438	if mode&InputMouse != 0 {
439		out.WriteString(funcs[t_enter_mouse])
440	} else {
441		out.WriteString(funcs[t_exit_mouse])
442	}
443
444	input_mode = mode
445	return input_mode
446}
447
448// Sets the termbox output mode. Termbox has four output options:
449//
450// 1. OutputNormal => [1..8]
451//    This mode provides 8 different colors:
452//        black, red, green, yellow, blue, magenta, cyan, white
453//    Shortcut: ColorBlack, ColorRed, ...
454//    Attributes: AttrBold, AttrUnderline, AttrReverse
455//
456//    Example usage:
457//        SetCell(x, y, '@', ColorBlack | AttrBold, ColorRed);
458//
459// 2. Output256 => [1..256]
460//    In this mode you can leverage the 256 terminal mode:
461//    0x01 - 0x08: the 8 colors as in OutputNormal
462//    0x09 - 0x10: Color* | AttrBold
463//    0x11 - 0xe8: 216 different colors
464//    0xe9 - 0x1ff: 24 different shades of grey
465//
466//    Example usage:
467//        SetCell(x, y, '@', 184, 240);
468//        SetCell(x, y, '@', 0xb8, 0xf0);
469//
470// 3. Output216 => [1..216]
471//    This mode supports the 3rd range of the 256 mode only.
472//    But you don't need to provide an offset.
473//
474// 4. OutputGrayscale => [1..26]
475//    This mode supports the 4th range of the 256 mode
476//    and black and white colors from 3th range of the 256 mode
477//    But you don't need to provide an offset.
478//
479// In all modes, 0x00 represents the default color.
480//
481// `go run _demos/output.go` to see its impact on your terminal.
482//
483// If 'mode' is OutputCurrent, it returns the current output mode.
484//
485// Note that this may return a different OutputMode than the one requested,
486// as the requested mode may not be available on the target platform.
487func SetOutputMode(mode OutputMode) OutputMode {
488	if mode == OutputCurrent {
489		return output_mode
490	}
491
492	output_mode = mode
493	return output_mode
494}
495
496// Sync comes handy when something causes desync between termbox's understanding
497// of a terminal buffer and the reality. Such as a third party process. Sync
498// forces a complete resync between the termbox and a terminal, it may not be
499// visually pretty though.
500func Sync() error {
501	front_buffer.clear()
502	err := send_clear()
503	if err != nil {
504		return err
505	}
506
507	return Flush()
508}
509