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