api.go

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