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